在相同时刻不可以领先10私有调用那一个点子,在同一时刻不可以超越10民用调用这些措施

现在,因为各类因素,你不可能不对一个呼吁或者措施开展频率上的造访限制。
比如说,
你对外提供了一个API接口,注册用户每分钟最多可以调用100次,非注册用户每分钟最多可以调用10次。
比如说,
有一个非常吃服务器资源的不二法门,在一如既往时刻无法领先10民用调用这个主意,否则服务器满载。
譬如, 有局部不同经常的页面,访客并无法屡屡的拜会或发言。
譬如说, 秒杀活动等举办。
譬如
,防范DDOS,当达到自然频率后调用脚本iis服务器ip黑名单,防火墙黑名单。
如上各样的比喻,也就是说,咋样从一个断面的角度对调用的点子开展频率上的范围。而对效用限制,服务器层面都有最直白的缓解办法,现在自己说的则是代码层面上的效用管控。

后天,因为各种因素,你必须对一个呼吁或者措施开展频率上的拜访限制。
譬如,
你对外提供了一个API接口,注册用户每秒钟最多可以调用100次,非注册用户每分钟最多可以调用10次。
比如,
有一个异常吃服务器资源的不二法门,在同一时刻无法领先10私有调用这些措施,否则服务器满载。
例如, 有一些特有的页面,访客并无法反复的拜访或发言。
诸如, 秒杀活动等开展。
例如
,防范DDOS,当达到一定频率后调用脚本iis服务器ip黑名单,防火墙黑名单。
如上各个的比喻,也就是说,咋样从一个断面的角度对调用的主意开展频率上的范围。而对功效限制,服务器层面都有最直白的化解方法,现在我说的则是代码层面上的效率管控。

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

正文给出多少个示范,一个是遵照单机环境的兑现,第二个则是据悉分布式的Redis实现



以第一个API接口需求为例,先说下单机环境下的贯彻。
依照惯性思维,大家自然会想到缓存的晚点策略这种措施,然则严厉来讲就HttpRuntime.Cache而言,通过缓存的过期策略来对请求进行频率的出现控制是不合适的。
  HttpRuntime.Cache
是应用程序级另外Asp.Net的缓存技术,通过这些技能可以发明四个缓存对象,能够为每个对象设置过期时间,当过期日子到达后该缓存对象就会没有(也就是当您拜访该目标的时候为Null)

以率先个API接口需求为例,先说下单机环境下的落实。
按照惯性思维,我们自然会想到缓存的晚点策略这种艺术,可是严峻来讲就HttpRuntime.Cache而言,通过缓存的逾期策略来对请求举办频率的出现控制是不合适的。
  HttpRuntime.Cache
是应用程序级其余Asp.Net的缓存技术,通过这一个技能可以表明多个缓存对象,可以为各样对象设置过期时间,当过期光阴到达后该缓存对象就会流失(也就是当您拜访该目的的时候为Null)

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

  为啥如此说吧?比如对某个方法(方法名:GetUserList)我们要开展1分钟最多10次的界定,现在我们就新建一个int型的Cache对象,然后设置1秒钟后过期消失。那么每当访问GetUserList方法前,大家就先判断这多少个Cache对象的值是否超出10,即便超越10就不执行GetUserList方法,尽管低于10则允许实施。每当访问该目的的时候即使不存在或者逾期就新建,这样循环,则该目的永远不容许超越10。

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   }
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   }

这般的研究及实现相对来说非凡简单,可是依据这样的一个模子设定,那么就会冒出那种情景:

那般的惦记及落实绝对来说非常简单,不过依照这样的一个模子设定,那么就会出现这种情状:

 图片 1

 图片 2

 

 

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

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

遵照这种简易缓存过期策略的模子,在这2分钟内,大家即使平均每分钟都访问了10次,满意那多少个确定,可是如果我们从中取一个里头段,0.5秒~1.5秒之内,也是1分钟,可是却的确的拜会了14次!远远领先了大家设置的
1秒钟最多访问10次的 限制。

基于那种简易缓存过期策略的模子,在那2分钟内,大家尽管平均每分钟都访问了10次,满足这么些规定,可是如果大家从中取一个里边段,0.5秒~1.5秒之内,也是1分钟,不过却的确的造访了14次!远远领先了俺们设置的
1分钟最多访问10次的 限制。

 

 

那么如何科学的来解决地方的题材吧?大家可以透过模拟对话级此外信号量这一手法,这也就是大家前天的要旨了。
   什么是信号量?仅就以代码而言,  static
SemaphoreSlim semaphoreSlim = new SemaphoreSlim(5); 
它的意思就意味着在多线程意况下,在其它一整日,只可以同时5个线程去拜谒。

那么哪些正确的来化解地点的题目吗?大家可以经过模拟对话级其它信号量这一手法,这也就是大家今日的核心了。
   什么是信号量?仅就以代码而言,  static
SemaphoreSlim semaphoreSlim = new SemaphoreSlim(5); 
它的情致就象征在多线程情状下,在任何一整日,只好同时5个线程去访问。

 

 

4容器4线程模型

前天,在实现代码的前边我们先规划一个模子。

图片 3

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

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

那么既然这些管道是会话级另外,我们肯定得需要一个容器,来装那些管道。现在,大家以IP来命名会话管道,并把拥有的管道都装载在一个容器中,如图

图片 4

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

 图片 5

自然,由于用户量多,一个器皿内或许存在上万个管道,那些时候只是用一个器皿来装载来清理,在效能上显著是不够的。这些时候,大家就得对容器举办横向扩充了。

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

图片 6

 

那么,最后就形成了我们的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线程安全而出的新品类
  里德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的。

 图片 7图片 8

4容器4线程模型

现今,在促成代码的前头咱们先规划一个模子。

图片 9

  假如咱们有一个用户A的管道,那么些管道里装着用户A的哀求,比如用户A在一秒钟发出了10次呼吁,那么每一个伸手过来,管道里的元素都会多一个。然则我们设定这个管道最两只好容纳10个要素,而且每个元素的存活期为1秒,1秒后则该因素消失。那么这样设计的话,无论是速率如故多少的突进,都会有管道长度的限制。这样一来,无论从哪一个光阴节点如故时间间隔出发,这些管道都能满意大家的频率限制需求。

而这里的管道,就必须和会话Id来对号入座了。每当有新会话进来的时候就生成一个新管道。那些会话id按照自己场景所定,可以是sessionId,可以是ip,也足以是token。

那么既然这一个管道是会话级其余,我们必定得需要一个器皿,来装这些管道。现在,大家以IP来定名会话管道,并把持有的管道都装载在一个容器中,如图

图片 10

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

 图片 11

本来,由于用户量多,一个容器内或许存在上万个管道,那一个时候只是用一个容器来装载来清理,在功能上肯定是不够的。那一个时候,大家就得对容器举行横向扩展了。

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

图片 12

 

这就是说,最终就形成了俺们的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线程安全而出的新类型
  ReaderWriterLockSlim:较里德rWriterLock优化的读写锁,五个线程同时做客读锁
或  一个线程访问写锁

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(), new ConcurrentList<DateTime>()); //如果不存在就新建 ConcurrentList
         return dtList.CounterAdd(10, DateTime.Now); //管道容量10,当临界管道容量后 返回false
     }
     finally
     {
         obj.ExitReadLock();
     }
 }

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

 而ConcurrentList,因为.Net没有推出List集合类的线程安全(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的。

 图片 13图片 14

分布式下Redis

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

这里就介绍分布式状况下,Redis的实现。

不同于Asp.Net的多线程模型,大概因为Redis的各类类型的元素非常粒度的操作导致各种加锁的复杂,所以在网络请求处理这块Redis是单线程的,基于Redis的兑现则因为单线程的来由在编码角度不用太多着想到与逻辑无关的问题。

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

前日要说的是Sorted
set有序聚集,有序聚集相比较其他的聚集类型的特殊点在于,使用有序聚集的时候还是能给插入的因素指定一个
积分score,大家把这些积分score理解为排连串,它其中会对积分举办排序,积分允许再一次,而不变聚集中的元素则是唯一。

  仍然一如既往的笔触,每当有用户访问的时候,都对该用户的
管道(有序聚集)中添加一个因素,然后设置该因素的积分为眼前时光。接着在程序中开个线程,来对管道中积分小于约定时辰的元素举办清理。因为规定有序聚集中的元素只好是唯一值,所以在赋值方面只倘使满意uuid即可。

 图片 15

这就是说用Redis来促成的代码这就是看似这种:

图片 16

透过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的实例来贯彻。最终放上效果图。

图片 17

最后,我把这多少个都给做成了个Demo。然而没有找到适当的上传网盘,所以大家可以留邮箱(留了就发),或者直接加QQ群文件自取,研讨交流:166843154

 

本身喜爱和本身一样的人交朋友,不被环境影响,自己是祥和的教工,欢迎加群
.Net web交换群, QQ群:166843154 欲望与挣扎

 

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

分布式下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即可。

 图片 18

那么用Redis来实现的代码那就是近似这种:

图片 19

经过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的实例来贯彻。最终放上效果图。

图片 20

最后,我把这个都给做成了个Demo。可是尚未找到合适的上传网盘,所以我们可以留邮箱(留了就发),或者间接加QQ群文件自取,商讨交换:166843154

 

自家喜欢和自身同一的人交朋友,不被环境影响,自己是和谐的教员,欢迎加群
.Net web互换群, QQ群:166843154 欲望与挣扎

 

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

相关文章