.Net 怎么样模拟会话级其余信号量,对http接口调用频率举办界定(有demo)

命令(Command)

分布式下Redis

上边介绍了一种频率限制的模子,分布式与单机相比,无非就是载体不同,大家只要把这一个容器的载体从程序上移植出来,来弄成一个独立的劳务或者直接借用Redis也是实惠的。

这边就介绍分布式情状下,Redis的贯彻。

不同于Asp.Net的多线程模型,大概因为Redis的各样类型的元素非凡粒度的操作造成各类加锁的错综复杂,所以在网络请求处理这块Redis是单线程的,基于Redis的落实则因为单线程的案由在编码角度不用太多考虑到与逻辑无关的问题。

  简单介绍下,Redis是一个内存数据库,这些数据库属于非关系型数据库,它的概念不同于一般的我们体会的Mysql
Oracle
SqlServer关系型数据库,它没有Sql没有字段名从未表名这个概念,它和HttpRun提姆(Tim)e.Cache的定义差不多一样,首先从操作上属于键值对形式,就如
Cache[“键名”]
这样就能博取到值类似,而且可以对各样Key设置过期策略,而Redis中的Key所对应的值并不是想存啥就存啥的,它襄助五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及sorted
set(有序聚集)。

明日要说的是Sorted
set有序聚集,有序聚集相相比其余的汇集类型的特殊点在于,使用有序聚集的时候仍可以给插入的因素指定一个
积分score,我们把这一个积分score领会为排系列,它其中会对积分举办排序,积分允许再一次,而一成不变聚集中的元素则是唯一。

  依然一样的思绪,每当有用户访问的时候,都对该用户的
管道(有序聚集)中添加一个要素,然后设置该因素的积分为眼前光阴。接着在先后中开个线程,来对管道中积分小于约定时辰的元素进行清理。因为规定有序聚集中的元素只可以是绝无仅有值,所以在赋值方面假如是满意uuid即可。

 图片 1

这就是说用Redis来促成的代码这就是相仿这种:

图片 2

透过using语法糖实现IDisposable而包装的Redis分布式锁,然后里面正常的逻辑判断。

这般的代码即便也能一气浑功用率,但不够自己。Redis是个基于内存的数据库,于性能而言,瓶颈在于网络
IO 上,与Get一回发生两回呼吁相比较,能不可以由此一段脚本来实现多数逻辑吗?

有的,Redis支持 Lua脚本:
  Lua
是一种轻量小巧的脚本语言,用规范C语言编写并以源代码情势开放,
其计划目标是为着放置应用程序中,从而为应用程序提供灵活的扩充和定制效能。
  大致意思就是,间接向Redis发送一段脚本或者让它一贯本地读取一段脚本从而直接促成所有的逻辑。

/// <summary>
/// 如果 大于10(AccountNum) 就返回1   否则就增加一条集合中的元素 并返回 空
/// </summary>
/// <param name="zcardKey"></param>
/// <param name="score"></param>
/// <param name="zcardValue"></param>
/// <param name="AccountNum"></param>
/// <returns></returns>
public string LuaAddAccoundSorted(string zcardKey, double score, string zcardValue, int AccountNum)
{
    string str = "local uu = redis.call('zcard',@zcardKey) if (uu >=tonumber(@AccountNum)) then return 1 else redis.call('zadd',@zcardKey,@score,@zcardValue)  end";
    var re = _instance.GetDatabase(_num).ScriptEvaluate(LuaScript.Prepare(str), new { zcardKey = zcardKey, score = score, zcardValue = zcardValue, AccountNum=AccountNum });
    return re.ToString();
}

local
uu就是表明一个为名uu的变量的意思,redis.call就是redis命令,那段脚本意思就是假设大于10(AccountNum) 就回去1   否则就大增一条集合中的元素 并重回 空。

管道内元素处理的点子就是:

 /// <summary>
 /// 遍历当前所有前缀的有序集合,如果数量为0,那么就返回1 否则 就删除 满足最大分值条件区间的元素,如果该集合个数为0则消失
 /// </summary>
 /// <param name="zcardPrefix"></param>
 /// <param name="score"></param>
 /// <returns></returns>
public string LuaForeachRemove(string zcardPrefix, double score)
 {
     StringBuilder str = new StringBuilder();
     str.Append("local uu = redis.call('keys',@zcardPrefix) "); //声明一个变量 去获取 模糊查询的结果集合
     str.Append("if(#uu==0) then");    //如果集合长度=0
     str.Append("   return 1 ");
     str.Append("else ");
     str.Append("   for i=1,#uu do ");   //遍历
     str.Append("       redis.call('ZREMRANGEBYSCORE',uu[i],0,@score) ");  //删除从0 到 该score 积分区间的元素
     str.Append("       if(redis.call('zcard',uu[i])==0) then ");  //如果管道长度=0
     str.Append("           redis.call('del',uu[i]) ");   //删除
     str.Append("       end ");
     str.Append("   end ");
     str.Append("end ");
     var re = _instance.GetDatabase(_num).ScriptEvaluate(LuaScript.Prepare(str.ToString()), new { zcardPrefix = zcardPrefix + "*", score = score });
     return re.ToString();

这2段代码通过发送Lua脚本的款式来成功了整个经过,因为Redis的网络模型原因,所以把LuaForeachRemove方法给指出来做个服务来单独处理即可。至于这种多容器多线程的贯彻,则完全可以开多少个Redis的实例来实现。最终放上效果图。

图片 3

末段,我把那么些都给做成了个Demo。可是尚未找到确切的上传网盘,所以我们可以留邮箱(留了就发),或者直接加QQ群文件自取,研讨交换:166843154

 

本身喜欢和自我同样的人交朋友,不被环境影响,自己是投机的名师,欢迎加群
.Net web互换群, QQ群:166843154 欲望与挣扎

 

作者:小曾
出处:http://www.cnblogs.com/1996V/p/8127576.html 欢迎转载,但任何转载必须保留完整文章及博客园出处,在显要地方显示署名以及原文链接。
.Net交流群, QQ群:166843154 欲望与挣扎 
4.点名命令目的

指令目的不是命令的特性而是命令源的属性。无论这些命令源是否享有焦点都会收到那些命令。假若没有点名命令目标,默认为当前主题对象就是命令目的。

那么什么样科学的来化解地点的问题吗?大家得以经过模拟对话级另外信号量这一手腕,这也就是我们前日的大旨了。
   什么是信号量?仅就以代码而言,  static
SemaphoreSlim semaphoreSlim = new SemaphoreSlim(5); 
它的情趣就表示在多线程意况下,在任何一整日,只可以同时5个线程去拜访。

1   if ((int)HttpRuntime.Cache["GetUserListNum"] > 10) //大于10请求失败
2   {
3      Console.WriteLine("禁止请求");
4   }
5   else
6   {
7      HttpRuntime.Cache["GetUserListNum"] = (int)HttpRuntime.Cache["GetUserListNum"] + 1; //否则该缓存对象的值+1
8      Console.WriteLine("允许请求");
9   }
2.指令绑定

明确:
·当命令被触发时执行咋样操作
·怎么着规定命令是否能被实践(可选)
·命令在什么地方起效用


5.设置命令关联(命令绑定)

WPf需要CommandBinding在执行前帮忙判断是不是足以推行,在实施后做一些风波来“打扫战场”。命令目的向命令关联发送路由事件,命令关联捕捉并拍卖路由事件,向命令反馈音讯。

依据这种概括缓存过期策略的模子,在这2分钟内,我们尽管平均每分钟都访问了10次,满意这多少个确定,然则一旦大家从中取一个期间段,0.5秒~1.5秒之内,也是1分钟,可是却实实在在的造访了14次!远远超越了俺们设置的
1分钟最多访问10次的 限制。

3.点名命令源

命令源拥有命令和命令源六个特性。
同一个限令可以有四个源。
要是把命令指派给命令源,命令源就会受命令影响,命令不可能实施的时候作为命令源的控件不可用。革命临官发送命令的措施不尽相同,例如单机双击。

最近,因为各个因素,你必须对一个伸手或者措施开展频率上的访问限制。
譬如,
你对外提供了一个API接口,注册用户每分钟最多可以调用100次,非注册用户每秒钟最多可以调用10次。
譬如,
有一个百般吃服务器资源的情势,在同等时刻无法超越10私房调用这一个方法,否则服务器满载。
例如, 有一对特殊的页面,访客并无法反复的走访或发言。
诸如, 秒杀活动等开展。
譬如说
,防范DDOS,当达到自然频率后调用脚本iis服务器ip黑名单,防火墙黑名单。
如上各个的比方,也就是说,咋样从一个断面的角度对调用的办法举行频率上的界定。而对效用限制,服务器层面都有最直白的化解格局,现在本身说的则是代码层面上的频率管控。

1.命令源

ICommandSource定义了六个特性:
·Command:指向连接的下令,必需
·CommandParameter:提供其他希望随命令发送的数码
·CommandTarget:确定将在里边执行命令的要素
譬如说:上边的按钮使用Command属性连接到ApplicationCommands.New命令:

<Button Command="ApplicationCommands.New">New</Button>

以率先个API接口需求为例,先说下单机环境下的兑现。
遵照惯性思维,我们本来会想到缓存的过期策略这种措施,不过严峻来讲就HttpRuntime.Cache而言,通过缓存的晚点策略来对请求举办频率的产出控制是不合适的。
  HttpRuntime.Cache
是应用程序级其它Asp.Net的缓存技术,通过这些技术可以注脚四个缓存对象,能够为每个对象设置过期时间,当过期时光到达后该缓存对象就会不复存在(也就是当您拜访该目的的时候为Null)

命令库

命令具有“一处讲明,处处使用”的特征。便携的命令库包括:ApplicationCommands,ComponentCommands,NavigationCommands,MediaCommands,EditingCommands.都是静态类。例如我们可以直接拿命令库用那个标准命令,不需要团结阐明:Open,Save,Play,Stop。(属于ApplicationCommand)

诸如此类的想想及落实相对来说相当简单,不过按照这样的一个模型设定,那么就会现出这种意况:

3.RoutedUICommand类继承自RoutedCommand类

用于所有文本的授命,只扩充了Text属性

正文给出六个示范,一个是基于单机环境的落实,第二个则是按照分布式的Redis实现

1.创立命令类

即拿到一个兑现ICommand接口的类,假如命令与具体工作逻辑无关,则应用WPF类库中的RoutedCommand即可。假如想得到与事务逻辑相关的专有命令,则需要创造RoutedCommand(或者ICommand)的派生类。

 

命令的利用

如上图,每个点代表一遍访问请求,我在0秒的时候
新建了一个名字为GetUserListNum的缓存对象。
在0~0.5秒之内
我访问了3次,在0.5~1秒之内,我们走访了7次。此时,该目的消失,然后我们随后访问,该对象重置为0.
              
 在第1~1.5秒之内,仍然访问了7次,在第1.5秒~2秒之内走访了3次。

自定义Command

———————待更新

4容器4线程模型

方今,在落实代码的前头大家先规划一个模子。

图片 4

  如若我们有一个用户A的管道,这些管道里装着用户A的呼吁,比如用户A在一分钟发出了10次呼吁,那么每一个呼吁过来,管道里的元素都会多一个。不过我们设定这些管道最四只好容纳10个要素,而且每个元素的存活期为1秒,1秒后则该因素消失。那么如此设计的话,无论是速率仍旧多少的突进,都会有管道长度的限制。这样一来,无论从哪一个时日节点如故时间距离出发,这些管道都能满意我们的效能限制要求。

而这边的管道,就务须和会话Id来对号入座了。每当有新会话进来的时候就生成一个新管道。这一个会话id依据自己场景所定,可以是sessionId,可以是ip,也得以是token。

这就是说既然这多少个管道是会话级其它,我们必然得需要一个容器,来装这个管道。现在,我们以IP来命名会话管道,并把富有的管道都装载在一个容器中,如图

图片 5

而遵照刚才的设定,大家还索要对容器内的每条管道的要素举行处理,把过期的给删除掉,为此,还需要独自为该容器开辟出一个线程来为每条管道展开元素的清理。而当管道的元素为0时,我们就清掉该管道,以便节省容器空间。

 图片 6

本来,由于用户量多,一个容器内或者存在上万个管道,这个时候偏偏用一个器皿来装载来清理,在效用上强烈是不够的。这些时候,我们就得对容器举行横向扩大了。

  比如,我们可以依据Cpu核心数自动生成对应的多少的容器,然后按照一个算法,对IP来拓展导流。我眼前cpu是4个逻辑主题,就生成了4个容器,每当用户访问的时候,都会首先经过一个算法,那么些算法会对IP进行处理,如192.168.1.11~192.168.1.13以此Ip段进第一个容器,xxx~xxx进第二个容器,依次类推,相应的,也就有了4个线程去分别处理4个容器中的管道。

图片 7

 

这就是说,最后就形成了大家的4容器4线程模型了。

今昔,着眼于编码实现:

  首先大家需要一个能承载这多少个器皿的载体,那个载体类似于连接池的定义,可以按照一些亟需自动生成适应数量的容器,即便有特殊要求的话,仍是可以够在容器上切出一个器皿管理的面,在线程上切出一个线程管理的面以便于实时监督和调度。尽管真要做这样一个系统,那么
容器的调度 和 线程的调度效用是少不了的,而本Demo则是水到渠成了至关紧要效率,像容器和线程在代码中本人也没剥离开来,算法也是间接写死的,实际设计中,对算法的筹划仍然很重要的,还有多线程模型中,怎么样上锁才能让功能最大化也是生死攸关的。

而这边为了案例的直观就径直写死成4个容器。

public static List<Container> ContainerList = new List<Container>(); //容器载体
static Factory()
{
     for (int i = 0; i < 4; i++)
     {
        ContainerList.Add(new Container(i));  //遍历4次  生成4个容器
     }
     foreach (var item in ContainerList)
     {
        item.Run();    //开启线程
     }
}

现行,大家假使 有编号为 0 到 40 这样的 41个用户。那么那个导流算法
我也就直接写死,编号0至9的用户
将他们的乞请给抛转到首个容器,编号10~19的用户
放到第二个容器,编号20~29放到第六个容器,编号30~40的用户放到第五个容器。

那么这么些代码就是如此的:

 static Container GetContainer(int userId, out int i) //获取容器的算法
 {
     if (0 <= userId && userId < 10)    //编号0至9的用户  返回第一个容器  依次类推
     {
          i = 0;
          return ContainerList[0];
     }
     if (10 <= userId && userId < 20)
     {
          i = 1;
          return ContainerList[1];
     }
     if (20 <= userId && userId < 30)
     {
          i = 2;
          return ContainerList[2];
      }
      i = 3;
      return ContainerList[3];
  }

当我们的对话请求经过算法的导流之后,都必须调用一个艺术,用于辨别管道数量。假诺管道数量一度超越10,则请求失利,否则成功

  public static void Add(int userId)
  {
       if (GetContainer(userId, out int i).Add(userId))
            Console.WriteLine("容器" + i + " 用户" + userId + "  发起请求");
       else
            Console.WriteLine("容器" + i + " 用户" + userId + "  被拦截");
  }

接下去就是容器Container的代码了。

此处,对容器的选型用线程安全的ConcurrentDictionary类。
  线程安全:当四个线程同时读写同一个共享元素的时候,就会油可是生数量错乱,迭代报错等安全问提
  ConcurrentDictionary:除了GetOrAdd方法要慎用外,是.Net4.0专为解决Dictionary线程安全而出的新品类
  里德(Reade)rWriterLockSlim:较ReaderWriterLock优化的读写锁,多少个线程同时做客读锁
或  一个线程访问写锁

private ReaderWriterLockSlim obj = new ReaderWriterLockSlim();  //在每个容器中申明一个读写锁
public ConcurrentDictionary<string, ConcurrentList<DateTime>> dic = new ConcurrentDictionary<string, ConcurrentList<DateTime>>(); //创建该容器 dic

下一场当你向容器添加一条管道中的数据是因而这些点子:

 public bool Add(int userId)
 {
     obj.EnterReadLock();//挂读锁,允许多个线程同时写入该方法
     try
     {
         ConcurrentList<DateTime> dtList = dic.GetOrAdd(userId.ToString(),t=>{ new ConcurrentList<DateTime>()}); //如果不存在就新建 ConcurrentList
         return dtList.CounterAdd(10, DateTime.Now); //管道容量10,当临界管道容量后 返回false
     }
     finally
     {
         obj.ExitReadLock();
     }
 }

 这里,为了在后头的线程遍历删除ConcurrentList的管道的时候保证ConcurrentList的安全性,所以这边要加读锁。

 而ConcurrentList,因为.Net没有推出List集合类的线程安全(此地我表明下:之所以不用ConcurrentBag是因为要保管count和add的一致性,这里补充一下),所以自己新建了一个继续于List<T>的安全项目,在此间
封装了3个需要动用的点子。

public class ConcurrentList<T> : List<T>
{
    private object obj = new object();

    public bool CounterAdd(int num, T value)
    {
        lock (obj)
        {
            if (base.Count >= num)
                return false;
            else
                base.Add(value);
            return true;
        }
    }
    public new bool Remove(T value)
    {
        lock (obj)
        {
            base.Remove(value);
            return true;
        }
    }
    public new T[] ToArray() 
    {
        lock (obj)
        {
            return base.ToArray();
        }
    }
}

说到底就是线程的周转情势:

 public void Run()
 {
     ThreadPool.QueueUserWorkItem(c =>
     {
         while (true)
         {
             if (dic.Count > 0)
             {
                 foreach (var item in dic.ToArray())
                 {
                     ConcurrentList<DateTime> list = item.Value;
                     foreach (DateTime dt in list.ToArray())   
                     {
                         if (DateTime.Now.AddSeconds(-3) > dt)
                         {
                             list.Remove(dt);
                             Console.WriteLine("容器" + seat + " 已删除用户" + item.Key + "管道中的一条数据");
                         }
                     }
                     if (list.Count == 0)
                     {
                         obj.EnterWriteLock();
                         try
                         {
                             if (list.Count == 0)
                             {
                                 if (dic.TryRemove(item.Key, out ConcurrentList<DateTime> i))
                                 { Console.WriteLine("容器" + seat + " 已清除用户" + item.Key + "的List管道"); }
                             }
                         }
                         finally
                         {
                             obj.ExitWriteLock();
                         }
                     }
                 }

             }
             else
             {
                 Thread.Sleep(100);
             }
         }
     }
   );
 }

末尾,是意义图,一个是基于控制台的,还一个是基于Signalr的。

 图片 8图片 9

2.声称命令实例

技术:一般情形下,只需要操作一个发令实例与之对应即可。由此先后中的命令多选取单件情势(Singletone
Pattern)以减掉代码复杂度。

 

2018/1/10 19:06:35

命令可以约束代码,仍可以够约束步骤逻辑。(事件的效能是发表和传播一些消息,对什么响应事件不做规定,每个接收者可以选择自己的所作所为来响应事件。也就是说事件不持有约束力)

  为啥如此说啊?比如对某个方法(方法名:GetUserList)大家要举行1分钟最多10次的限量,现在我们就新建一个int型的Cache对象,然后设置1分钟后过期消失。那么每当访问GetUserList方法前,我们就先判断那个Cache对象的值是否超越10,固然过量10就不履行GetUserList方法,假如低于10则允许实施。每当访问该对象的时候假若不设有或者逾期就新建,这样循环,则该对象永远不容许超越10。

指令实例:

概念一个指令,使用Button来发送那些命令,当命令送达TextBox时,TextBox被清空(假若没有文字则不发送命令)
·界面代码:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Command"
    Background="LightBlue" Height="350" Width="525">
<StackPanel x:Name="stackPanel">
    <Button x:Name="button1" Content="Send Command" Margin="5"/>
    <TextBox x:Name="textBoxA" Margin="5,0" Height="100"/>
</StackPanel>
</Window>

·后台代码:

//实现:定义一个命令,使用Button来发送这个命令,当命令送达TextBox时,TextBox被清空(如果没有文字则不发送命令)

namespace WpfApplication1
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        InitializeCommand();
    }
    //声明并定义命令
    private RoutedCommand clearCmd = new RoutedCommand("CLear", typeof(MainWindow));

    private void InitializeCommand()
    {
        //把命令赋值给命令源(发送者)并指定快捷键
        this.button1.Command = this.clearCmd;
        this.clearCmd.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Alt));//按Alt+C键

        //指定命令目标
        this.button1.CommandTarget = this.textBoxA;

        //创建命令关联
        CommandBinding cb = new CommandBinding();
        cb.Command = this.clearCmd;//只关注与clearCmd相关的事件
        cb.CanExecute += new CanExecuteRoutedEventHandler(cb_CanExecute);
        cb.Executed +=new ExecutedRoutedEventHandler(cb_Executed);

        //把命令关联安置在外围控件上
        this.stackPanel.CommandBindings.Add(cb);
    }

    //当探测命令是否可以执行时,此方法被调用
    void cb_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        if(string.IsNullOrEmpty(this.textBoxA.Text))
        { e.CanExecute = false; }
        else
        { e.CanExecute = true; }

        //避免继续向上传而降低程序性能
        e.Handled = true;
    }

    //当命令送达目标后,此方法被调用
    void cb_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        this.textBoxA.Clear();
        //避免继续向上传而降低程序性能
        e.Handled = true;
    }
}

解释:
·运行程序,在TextBox中输入文字后Button在命令可进行情况的场合下变成可用,此时单击Button或者按Alt+C键,TextBox都会被清空。
·RoutedCommand并不对命令目的做其他操作,而是由CommandBinding操作的。
·无论是探测命令是否履行或者命令送达目的,都会激发命令目的发送路由事件,这么些路由事件会沿着UI元素树向上传递并最终被CommandBinding所捕捉。
·CommandBinding被安装在外面的StackPanel上,CommandBinding起一个侦听器的法力,而且特别针对clearCmd命令捕捉与其休戚相关的路由事件。
·当CommandBinding捕捉到CanExecute事件,就会调用cb_CanExecute方法判断命令执行尺度是否知足,并上报给命令供其影响命令源的情状;当捕捉到Execute事件,表示命令的Execute方法已经履行了,或者说命令已经意义在了命令目的上(RoutedCommand只担负让命令目标激发Executed),则调用Executed方法。
·CommandBingding一定要安装在命令目的的外场控件上,不然不可能捕捉到CanExecute和Executed等路由事件。

 

1.ICommand接口包含六个方法和一个事件

Execute()方法包含应用程序逻辑(例如打印文档);
CanExecute()方法再次来到命令状态(命令可用再次来到true,不可用再次来到false);
CanExecuteChanged事件,命令状态改变时引发,那是指示信号,表示应该调用CanExecute()检查命令状态。

 图片 10

执行命令

RoutedUIIcommand类没有其他编码的意义,只表示命令。为触发命令,需要有命令源(也可利用代码)。为响应命令,需要有指令绑定,命令绑定将执行转发给普通的事件处理程序。

2.RoutedCommand类自动实现ICommmand接口的类

不带有其他应用程序逻辑,只代表命令。为事件冒泡和隧道添加了部分分外的基础结构

一声令下系统的为主元素

·命令(Command):实际上就是促成了ICommand接口的类,平常使用最多的是RoutedCommand类
·命令源(Command Source):实现了ICommandResource接口的类(Button)
·命令目的(Command Target):必须是实现了IInputCommand接口的类
·命令关联(Command
Binding):负责把外围逻辑与命令关联起来,比如执行从前对命令是否可以执行举行判断、命令执行后还有什么样后续工作等

发表评论

电子邮件地址不会被公开。 必填项已用*标注