限流算法的实现(redis + lua)

限流算法

常见的限流算法

  • 计数器算法
  • 漏桶算法
  • 令牌桶算法

计数器算法

  顾名思义,计数器算法是指在一定的时间窗口内允许的固定数量的请求.比如,2s内允许10个请求,30s内允许100个请求等等.如果设置的时间粒度越细,那么相对而言限流就会越平滑,控制的粒度就会更细.

场景分析

试想,如果设置的粒度比较粗会出现什么样的问题呢?如下图设置一个 1000/3s 的限流计数统计.
计数器时间轴

图中的限流策略为3s内允许的最大请求量为1000,那么会出现2个极端:
极端情况1:
  第1s请流量为10,
  第2s请流量为10,
  第3s请流量突然激增到980.这意味着在这一刻,有大量的请求蜂拥而至,假设服务每秒能处理的上线为800/1s,但是此刻却有超过这个量级的请求量,那么后果是不堪设想的.

极端情况2:
  第1s请流量突然就达到990,
  留给后续第2s,3s的可请求数量就非常少了,可能会出现大量的拒绝请求.

结论:
  如果用统计计数算法,尽量保持粒度切割精细.

算法实现

redis的ttl特性完美的满足了这一需求,将时间窗口设置为key的失效时间,然后将key的值每次请求+1即可.伪代码实现思路:

//1.判断是否存在该key
if(EXIT(key)){
  // 1.1自增后判断是否大于最大值,并返回结果
  if(INCR(key) > maxPermit){
     return false;
  }
 return true;
}

//2.不存在key,则设置key初始值为1,失效时间为3秒
SET(KEY,1);
EXPIRE(KEY,3);

漏铜算法

漏桶算法核心概念:

  • 桶的容量是固定的,并且水流以一个固定的速率流出;
  • 流入的水流可以是任意速率;
  • 如果流入的水流超出了桶的容量,则后续流入的水流溢出(请求被丢弃)。
  • 如果桶内没有水,则不需要流出

漏桶算法
缺点:
不难想象漏桶算法并不能很好的应对突发的流量限制,在某一个时间段流量激增,则漏桶算法处理就比较无能为力.这个时候就需要用到和他相反设计的令牌桶算法

令牌桶算法:

令牌桶

如上图所示,整个请求流程一目了然.简单概括如下:

1.用户请求资源时首选从桶里获取令牌,如果有令牌则放行,如此同时桶里的令牌数量-1
2.于此同时,以一定的速率往桶里加入令牌,这个速度是可根据实际场景随意设置.

算法实现

var key;
var maxPermit;//桶的容量,即最大请求限制
var expire;//失效时间

var bucketInterval;//每次向桶里添加令牌的时间间隔
var bucketNum;//每次向桶里添加令牌的个数

var lastTimeKey = key +"last";//标记上一次操作时间


//判断是否存在该key
if(EXIT(key)){
  var value = GET(key);
  var diffTime = now() - lastTimeKey;
  // 1.1判断是否超出时间间隔
  if(diffTime  > bucketInterval){
      // 1.2根据时间间隔,计算出应该向桶里添加令牌的个数
      local maxValue = value+math.floor(diff/interval)*step;
      if (maxValue > limit)
         value = limit;
      else
         value = maxValue;
     //设置key的值及操作时间
     SET(key,value);
     SET(lastTimeKey,now());     
  }
  
  // 2.1在时间间隔内,判断桶里是否有值
  if(value <= 0){
     reurn false;
  }else{
    // 2.2 减1
    DECR(key);
  }
reture true;
}


//2.不存在key,则设置key初始值为maxPermit-1
SET(key,maxPermit-1);
EXPIRE(lastTimeKey,now());

  上面实现代码只是伪代码,提供的是一种思路而已. 仔细想来其中某个环节其实并不完美.大家可以参考Guava的ratelimit实现思路,他的限流就是基于令牌桶算法,但是比较遗憾的是在单机下的限流.
思考:
  就是时间间隔如果过长的话,一次性向桶里添加的令牌数量则是桶的最大容量!那么某个时间的瞬间请求过来,服务器的压力是非常大的.
所以此处增加令牌数可以设置的稍微合理些,哪怕间隔时间再长!

代码稍后上传


微服务实践
基于微服务的实践方案及踩坑之路

有些人不离开你,你永远都长不大

89 声望
22 粉丝
0 条评论
推荐阅读
springboot 动态数据源(Mybatis+Druid)
Spring多数据源实现的方式大概有2中,一种是新建多个MapperScan扫描不同包,另外一种则是通过继承AbstractRoutingDataSource实现动态路由。今天作者主要基于后者做的实现,且方式1的实现比较简单这里不做过多探讨。

时光沉旧了少年6阅读 12.7k

Redis 发布订阅模式:原理拆解并实现一个消息队列
“65 哥,如果你交了个漂亮小姐姐做女朋友,你会通过什么方式将这个消息广而告之给你的微信好友?““那不得拍点女朋友的美照 + 亲密照弄一个九宫格图文消息在朋友圈发布大肆宣传,暴击单身狗。”像这种 65 哥通过朋...

码哥字节6阅读 1.9k

封面图
Redis的线程模型和事务
我原本只是想学习Redis的事务,但后来发现,Redis和传统关系型数据库的事务在ACID的表现上差异很大。而要想详细了解其中的缘由,就离不开Redis独特的单线程模型,因此本文将二者联系在一起讲解。

KerryWu6阅读 5.9k评论 2

Redis分布式锁的实现
很多新手将 分布式锁 和 分布式事务 混淆,个人理解:锁 是用于解决多程序并发争夺某一共享资源;事务 是用于保障一系列操作执行的一致性。我前面有几篇文章讲解了分布式事务,关于2PC、TCC和异步确保方案的实现...

KerryWu4阅读 6.8k评论 2

又一款内存数据库横空出世,比 Redis 更强,性能直接飙升一倍!杀疯了
KeyDB是Redis的高性能分支,专注于多线程,内存效率和高吞吐量。除了多线程之外,KeyDB还具有仅在Redis Enterprise中可用的功能,例如Active Replication,FLASH存储支持以及一些根本不可用的功能,例如直接备份...

民工哥4阅读 918评论 1

封面图
详解Redisson分布式限流的实现原理
&emsp;&emsp;我们目前在工作中遇到一个性能问题,我们有个定时任务需要处理大量的数据,为了提升吞吐量,所以部署了很多台机器,但这个任务在运行前需要从别的服务那拉取大量的数据,随着数据量的增大,如果同时...

xindoo3阅读 894

封面图
redis 学习笔记
一 搭建 {代码...} 二 数据类型,常用操作命令 {代码...} {代码...} 三 redis常用命令 {代码...} 四 Redis高级应用 {代码...} 五 PHP-REDIS使用 {代码...} 六 BitMap 教程 {代码...}

hufeng2阅读 2.2k

有些人不离开你,你永远都长不大

89 声望
22 粉丝
宣传栏