365体育网投未注册用户每秒钟最多得调用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   }

这样的琢磨和实现相对来说非常简单,但是根据这样的一个模设定,那么就见面出现这种情况:

诸如此类的思辨及落实相对来说非常简单,但是因这样的一个型设定,那么尽管会见并发这种情景:

 365体育网投 1

 365体育网投 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线程模型

现,在落实代码的事先我们先行筹一个型。

365体育网投 3

  假设我们发一个用户A的管道,这个管道里装着用户A的呼吁,比如用户A在同等秒钟发出了10不成呼吁,那么每一个要过来,管道里的元素还见面多一个。但是咱设定是管道最多只能容纳10单元素,而且每个元素的存活期为1秒,1秒后虽该因素消失。那么如此设计吧,无论是速率还是多少的突进,都见面发管道长度的限量。这样一来,无论从哪一个时日节点还是时间间隔出发,这个管道还能够满足我们的频率限制要求。

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

那么既然这管道是会说话级别之,我们一定得用一个器皿,来装这些管道。现在,我们盖IP来定名会话管道,并将有的管道还装在一个容器中,如图

365体育网投 4

比方据悉刚才之设定,我们尚待对容器内的诸条管道的素进行处理,把过的被删除掉,为夫,还需单独为该容器开辟有一个线程来吗各个条管道展开元素的清理。而当管道的因素呢0时,我们就到底掉该管道,以便节省容器空间。

 365体育网投 5

当,由于用户量多,一个器皿内可能是上万个管道,这个上偏偏用一个容器来装来清理,在效率上显眼是不够的。这个时刻,我们就算得对容器进行横向扩张了。

  比如,我们得以根据Cpu核心数自动生成对应之数之器皿,然后根据一个算法,对IP来进展导流。我当下cpu是4独逻辑核心,就异常成了4个容器,每当用户访问的时节,都见面最先经过一个算法,这个算法会对IP进行拍卖,如192.168.1.11~192.168.1.13这个Ip段进第一个容器,xxx~xxx进第二独容器,依次类推,相应的,也尽管发生矣4只线程去分别处理4独容器中之管道。

365体育网投 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线程安全要起之初路
  ReaderWriterLockSlim:较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的。

 365体育网投 7365体育网投 8

4容器4线程模型

当今,在落实代码的事先我们先筹一个型。

365体育网投 9

  假设我们来一个用户A的管道,这个管道里装在用户A的要,比如用户A在一如既往秒钟发出了10涂鸦呼吁,那么每一个请求过来,管道里之要素都见面多一个。但是我们设定是管道最多只能容10个因素,而且每个元素的存活期为1秒,1秒后虽说该因素消失。那么这样设计吧,无论是速率还是多少之突进,都见面发管道长度的界定。这样一来,无论由哪一个日节点还是时间距离出发,这个管道还能够满足我们的效率限制要求。

如果此的管道,就不能不跟会话Id来对号入座了。每当发生新会话进来的时即便那个成一个初管道。这个会话id根据自己场景所必然,可以是sessionId,可以是ip,也足以是token。

那既然这管道是碰头讲话级别之,我们必定得待一个器皿,来装这些管道。现在,我们因为IP来定名会话管道,并拿所有的管道都装在一个容器中,如图

365体育网投 10

设依据刚才之设定,我们还需要针对容器内之每条管道的元素进行拍卖,把过的为抹掉,为这个,还待单独为该容器开辟出一个线程来为各个条管道进行元素的清理。而当管道的元素呢0时,我们就是干净掉该管道,以便节省容器空间。

 365体育网投 11

自,由于用户量基本上,一个器皿内或许存在上万单管道,这个时节就用一个器皿来装来清理,在效率达显然是不够的。这个上,我们不怕得对容器进行横向扩张了。

  比如,我们得以因Cpu核心数自动生成对应之数之器皿,然后根据一个算法,对IP来拓展导流。我手上cpu是4单逻辑核心,就老大成了4只容器,每当用户访问的时刻,都见面最先经过一个算法,这个算法会对IP进行处理,如192.168.1.11~192.168.1.13夫Ip段进第一只容器,xxx~xxx进第二个容器,依次类推,相应的,也不怕来了4只线程去分别处理4只容器被的管道。

365体育网投 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:较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(), 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的。

 365体育网投 13365体育网投 14

分布式下Redis

方介绍了平等种频率限制的范,分布式与单机相比,无非就是是载体不同,我们设把这个容器的载体从程序上移植出来,来整治成一个独门的劳务或者直接借用Redis也是卓有成效之。

这边虽介绍分布式情况下,Redis的贯彻。

不等于Asp.Net的多线程模型,大概因为Redis的各种类型的素非常粒度的操作造成各种加锁之纷繁,所以当网要处理这块Redis是单线程的,基于Redis的兑现则坐单线程的因由在编码角度不用太多考虑到跟逻辑无关的题材。

  简单介绍下,Redis是一个内存数据库,这个数据库属于非关系型数据库,它的概念不同于一般的我们体会的Mysql
Oracle
SqlServer关系型数据库,它从未Sql没有字段名尚未表名这些概念,它和HttpRunTime.Cache的定义差不多一样,首先由操作及属于键值对模式,就设
Cache[“键名”]
这样即便能收获到价值类似,而且好本着每个Key设置过策略,而Redis中之Key所对应之价值并无是怀念存啥就存啥的,它支持五栽多少列:string(字符串),hash(哈希),list(列表),set(集合)及sorted
set(有序聚集)。

今天只要说之是Sorted
set有序聚集,有序聚集相比其他的汇类型的非常规点在,使用有序聚集的时还会叫插入的素指定一个
积分score,我们拿这积分score理解为祛序列,它里面会指向积分进行排序,积分允许再,而不变聚集中的素虽然是绝无仅有。

  还是一如既往的笔触,每当发生用户访问的时,都针对拖欠用户之
管道(有序聚集)中补充加一个元素,然后设置该因素的积分也即时间。接着在次中初露个线程,来对管道中积分小于约定时间的元素进行清理。因为规定有序聚集中的因素只能是唯一值,所以在赋值方面而是满足uuid即可。

 365体育网投 15

那因此Redis来贯彻之代码那就是是近乎这种:

365体育网投 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的实例来促成。最后放上功能图。

365体育网投 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没有字段名无表名这些概念,它同HttpRunTime.Cache的定义差不多一样,首先从操作及属于键值对模式,就假设
Cache[“键名”]
这样虽可知取到价值类似,而且可以对每个Key设置过策略,而Redis中之Key所对应之价值并无是想存啥就存啥的,它支持五种多少类:string(字符串),hash(哈希),list(列表),set(集合)及sorted
set(有序聚集)。

今日若说的凡Sorted
set有序聚集,有序聚集相比另的会师类型的非常点在于,使用有序聚集的早晚还能于插入的素指定一个
积分score,我们把此积分score理解呢清除序列,它里面会对积分进行排序,积分允许再,而有序聚集中之素虽然是唯一。

  还是一样的思绪,每当发生用户访问的早晚,都对准该用户的
管道(有序聚集)中上加一个素,然后设置该因素的积分为眼前岁月。接着以程序中启个线程,来针对管道被积分小于约定时间之素进行清理。因为规定有序聚集中的因素只能是绝无仅有值,所以于赋值方面要是满足uuid即可。

 365体育网投 18

那么用Redis来促成的代码那便是类似这种:

365体育网投 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的实例来落实。最后放上力量图。

365体育网投 20

最后,我拿这些都吃做成了只Demo。但是从未找到适合的上传网盘,所以大家可以留邮箱(留了就是作),或者直接加QQ群文件自取,讨论交流:166843154

 

自家欢喜与自身一样的人口交朋友,不为环境影响,自己是友善的良师,欢迎加群
.Net web交流群, QQ群:166843154 欲望与挣扎

 

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

相关文章