服务器三种常见的限流算法

  • 1、计数器算法
  • 2. 滑动窗口
  • 3、令牌桶算法
  • 4、漏桶算法
  • 滑动窗口限流
  • 漏桶限流
  • 令牌桶限流
  • 限流算法总结
  • 单机限流和分布式限流
  • 限流组件

在开发高并发系统时,有三把利器用来保护系统:缓存、降级和限流。那么何为限流呢?顾名思义,限流就是限制流量,就像你宽带包了1个G的流量,用完了就没了。通过限流,我们可以很好地控制系统的qps,从而达到保护系统的目的。本篇文章将会介绍一下常用的限流算法以及他们各自的特点。

1、计数器算法

计数器算法是限流算法里最简单也是最容易实现的一种算法。

比如我们规定,对于A接口来说,我们1分钟的访问次数不能超过100个。

那么我们可以这么做:在一开 始的时候,我们可以设置一个计数器counter,每当一个请求过来的时候,counter就加1,如果counter的值大于100并且该请求与第一个 请求的间隔时间还在1分钟之内,那么说明请求数过多;

如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置 counter,具体算法的示意图如下:

具体的伪代码如下:

public class CounterTest {public long timeStamp = getNowTime();public int reqCount = 0;public final int limit = 100; // 时间窗口内最大请求数public final long interval = 1000; // 时间窗口mspublic boolean grant() {long now = getNowTime();if (now < timeStamp + interval) {// 在时间窗口内reqCount++;// 判断当前时间窗口内是否超过最大请求控制数return reqCount <= limit;} else {timeStamp = now;// 超时后重置reqCount = 1;return true;}}public long getNowTime() {return System.currentTimeMillis();}
}

这个算法虽然简单,但是有一个十分致命的问题,那就是临界问题,我们看下图:

从上图中我们可以看到,假设有一个恶意用户,他在0:59时,瞬间发送了100个请求,并且1:00又瞬间发送了100个请求,那么其实这个用户在 1秒里面,瞬间发送了200个请求。

我们刚才规定的是1分钟最多100个请求,也就是每秒钟最多1.7个请求,用户通过在时间窗口的重置节点处突发请求, 可以瞬间超过我们的速率限制。用户有可能通过算法的这个漏洞,瞬间压垮我们的应用。

聪明的朋友可能已经看出来了,刚才的问题其实是因为我们统计的精度太低。那么如何很好地处理这个问题呢?或者说,如何将临界问题的影响降低呢?我们可以看下面的滑动窗口算法。

2. 滑动窗口

滑动窗口,又称rolling window。为了解决这个问题,我们引入了滑动窗口算法。如果学过TCP网络协议的话,那么一定对滑动窗口这个名词不会陌生。下面这张图,很好地解释了滑动窗口算法:

在上图中,整个红色的矩形框表示一个时间窗口,在我们的例子中,一个时间窗口就是一分钟。

然后我们将时间窗口进行划分,比如图中,我们就将滑动窗口划成了6格,所以每格代表的是10秒钟。每过10秒钟,我们的时间窗口就会往右滑动一格。

每一个格子都有自己独立的计数器counter,比如当一个请求 在0:35秒的时候到达,那么0:30~0:39对应的counter就会加1。

那么滑动窗口怎么解决刚才的临界问题的呢?

我们可以看上图,0:59到达的100个请求会落在灰色的格子中,而1:00到达的请求会落在橘黄色的格 子中。

当时间到达1:00时,我们的窗口会往右移动一格,那么此时时间窗口内的总请求数量一共是200个,超过了限定的100个,所以此时能够检测出来触 发了限流。

我再来回顾一下刚才的计数器算法,我们可以发现,计数器算法其实就是滑动窗口算法。只是它没有对时间窗口做进一步地划分,所以只有1格。

由此可见,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。

3、令牌桶算法

令牌桶算法是比较常见的限流算法之一,大概描述如下:

所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
根据限流大小,设置按照一定的速率往桶里添加令牌;
桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流;

4、漏桶算法

漏桶算法其实很简单,可以粗略的认为就是注水漏水过程,往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。

常见的限流算法主要有:计数器、固定窗口,滑动窗口、漏桶、令牌桶。接下来我们分别介绍下这几种限流算法。

计数器限流
计数器限流是最简单粗暴的一种限流算法,例如系统能同时处理100个请求,那么可以在保存一个计数器,处理一个请求,计数器加一,一个请求处理完毕后计数器减一。每次请求进来的时候,先看一眼计数器的值,如果超过阀值则直接拒绝。

在具体实现时,如果该计数器是存在单机内存中,那么就实现了单机限流;而如果存在例如Redis中,集群中的所有节点依次为限流依据,那么就算实现了集群限流算法。

优点:实现简单,单机例如诸如Java的Atomic等原子类就能实现,集群则通过Redis的incr操作就能快速实现。

缺点:计数器限流无法应对突发的流量增长。例如我们允许的阀值是1W,此时计数器的值是0,那么当1W个请求瞬间全部打进来的时候,很可能服务就顶不住了。这是因为流量的缓缓增加和一下子涌入,对系统所产生的压力是不一样的。

况且一般限流都是限制在指定时间间隔内的访问量,而不是全时段服务的总体处理能力,所以计数器限流不太适合高并发场景下的限流实现。

固定窗口限流
相对于计数器来说,固定窗口限流是以一段时间窗口内的访问量作为限流的依据,计数器每过一个时间窗口就自动重置。其规则如下:

请求次数小于阀值,允许访问,计数器加1;
请求次数大于阀值,拒绝访问;
本时间窗口过了之后,计数器自动清零;

固定窗口限流虽然看起来挺完美,但是它有固定窗口临界的问题。

例如系统每秒允许1000个请求,假如第一个时间窗口的间隔是0~1秒,但在第0.55秒处一下子涌入了1000个请求,过了1秒后计数清零,此时在1.05秒的时候又一下子涌入了1000个请求。

此时虽然在固定时间窗口内的计数没有超过阀值,但在全局看来0.55秒~1.05秒这0.5秒内一下子却涌入了2000个请求,而这对于阀值为1000/s的系统来说是不可承受的。如下图所示:

而为了解决这个问题,衍生出了滑动窗口限流的算法!

滑动窗口限流

滑动窗口限流解决了固定窗口临界值的问题,可以保证任意时间窗口内都不会超过限流阀值。

相对于固定窗口,滑动窗口除了需要引入计数器外,还需要额外记录时间窗口内每个请求到达的时间点。

以时间窗口为1秒为例,规则如下:

记录每次请求的时间;
统计每次请求的时间向前推1秒这个时间窗口内的请求数,且1秒前的数据可以删除;
统计的请求数小于阀值则记录该请求的时间,并允许通过,反之则拒绝该请求;

虽然看起来很OK,但是滑动窗口也无法解决短时间之内集中流量的冲击。

例如每秒限制1000个请求,但是有可能存在前5毫秒的时候,阀值就被打满的情况,理想情况下每10毫秒来100个请求,那么系统对流量的处理就会更加平滑。

但在真实场景中是很难控制请求的频率的。所以为了解决时间窗口类算法的痛点,又出现了漏桶算法。

漏桶限流

漏桶算法的基本思想是,流量持续进入漏桶中,底部则定速处理请求,如果流量进入的速率高于底部请求被处理的速率,且当桶中的流量超过桶的大小时,流量就会被溢出。具体如下图所示:

漏桶算法的特点是宽进严出,无论请求的速率有多大,底部的处理速度都匀速进行。这种算法的特点有点类似于消息队列的处理机制,一般来说漏桶算法也是由队列来实现的。

但漏桶算法的这种特点,实际上即是它的优点也是缺点。

有时候面对突发流量,我们往往会希望在保持系统稳定的同时,能更快地处理用户请求以提升用户体验,而不是按部就班的佛系工作。

在这种情况下又出现了令牌桶这样的限流算法,它在应对突发流量时,可以比漏桶算法更加激进。

令牌桶限流

令牌桶与漏桶的原理类似,只是漏桶是底部匀速处理,而令牌桶则是定速的向桶里塞入令牌,然后请求只有拿到了令牌才会被服务器处理。

具体规则如下:

定速的向桶中放入令牌;
令牌数量超过桶的限制,则丢弃;
请求来了先向桶中索取令牌,索取成功则通过被处理,否则拒绝;

可以看出令牌桶在应对突发流量时,不会想漏桶那样匀速的处理,而是在短时间内请求可以同时取走桶中的令牌,并及时的被服务器处理。所以在应对突发流量的场景下,令牌桶表现更强。

限流算法总结

经过上述的描述,好像漏桶、令牌桶比时间窗口类算法好多了,那么时间窗口类算法是不是就没啥用了呢?

其实并不是,虽然漏桶、令牌桶对比时间窗口类算法对流量的整形效果更好,但是它们也有各自的缺点,
例如令牌桶,假如系统上线时没有预热,那么可能会出现由于此时桶中还没有令牌,而导致请求被误杀的情况;
而漏桶中由于请求是暂存在桶中的,所以请求什么时候能被处理,则是有延时的,这并不符合互联网业务低延时的要求。
所以令牌桶、漏桶算法更适合阻塞式限流的场景,即后台任务类的限流。

而基于时间窗口的限流则更适合互联网实施业务限流的场景,即能处理快速处理,不能处理及时响应调用方,避免请求出现过长的等待时间。

微服务限流组件
如果你有兴趣实际上也是可以自己实现一个限流组件的,只不过这种轮子已经早有人造好了。

目前市面上比较流行的限流组件主要有:Google Guava提供的限流工具类“RateLimiter”、阿里开源的Sentinel。

其中Google Guava提供的限流工具类“RateLimiter”,是基于令牌桶实现的,并且扩展了算法,支持了预热功能。

而阿里的Sentinel中的匀速限流策略,就是采用了漏桶算法。

单机限流和分布式限流

本质上单机限流和分布式限流的区别其实就在于 “阈值” 存放的位置。

单机限流就上面所说的算法直接在单台服务器上实现就好了,而往往我们的服务是集群部署的。因此需要多台机器协同提供限流功能。

像上述的计数器或者时间窗口的算法,可以将计数器存放至 Tair 或 Redis 等分布式 K-V 存储中。

例如滑动窗口的每个请求的时间记录可以利用 Redis 的 zset 存储,利用ZREMRANGEBYSCORE 删除时间窗口之外的数据,再用 ZCARD计数。

像令牌桶也可以将令牌数量放到 Redis 中。

不过这样的方式等于每一个请求我们都需要去Redis判断一下能不能通过,在性能上有一定的损耗,所以有个优化点就是 「批量」。例如每次取令牌不是一个一取,而是取一批,不够了再去取一批。这样可以减少对 Redis 的请求。

不过要注意一点,批量获取会导致一定范围内的限流误差。比如你取了 10 个此时不用,等下一秒再用,那同一时刻集群机器总处理量可能会超过阈值。

其实「批量」这个优化点太常见了,不论是 MySQL 的批量刷盘,还是 Kafka 消息的批量发送还是分布式 ID 的高性能发号,都包含了「批量」的思想。

当然分布式限流还有一种思想是平分,假设之前单机限流 500,现在集群部署了 5 台,那就让每台继续限流 500 呗,即在总的入口做总的限流限制,然后每台机子再自己实现限流。

限流的难点
可以看到每个限流都有个阈值,这个阈值如何定是个难点。

定大了服务器可能顶不住,定小了就“误杀”了,没有资源利用最大化,对用户体验不好。

我能想到的就是限流上线之后先预估个大概的阈值,然后不执行真正的限流操作,而是采取日志记录方式,对日志进行分析查看限流的效果,然后调整阈值,推算出集群总的处理能力,和每台机子的处理能力(方便扩缩容)。

然后将线上的流量进行重放,测试真正的限流效果,最终阈值确定,然后上线。

我之前还看过一篇耗子叔的文章,讲述了在自动化伸缩的情况下,我们要动态地调整限流的阈值很难,于是基于TCP拥塞控制的思想,

根据请求响应在一个时间段的响应时间P90或者P99值来确定此时服务器的健康状况,来进行动态限流。

在他的 Ease Gateway 产品中实现了这套算法,有兴趣的同学可以自行搜索。

其实真实的业务场景很复杂,需要限流的条件和资源很多,每个资源限流要求还不一样。所以我上面就是嘴强王者。

限流组件

一般而言我们不需要自己实现限流算法来达到限流的目的,不管是接入层限流还是细粒度的接口限流其实都有现成的轮子使用,其实现也是用了上述我们所说的限流算法。

比如Google Guava 提供的限流工具类 RateLimiter,是基于令牌桶实现的,并且扩展了算法,支持预热功能。

阿里开源的限流框架Sentinel 中的匀速排队限流策略,就采用了漏桶算法。

Nginx 中的限流模块 limit_req_zone,采用了漏桶算法,还有 OpenResty 中的 resty.limit.req库等等。

具体的使用还是很简单的,有兴趣的同学可以自行搜索,对内部实现感兴趣的同学可以下个源码看看,学习下生产级别的限流是如何实现的。

服务器三种常见的限流算法相关推荐

  1. 常见的限流算法及其原理剖析

    1 何谓限流 限流,顾名思义,便是限制流量的意思.系统规定在一段时间内只能进入这么多的流量,如果超过限额的话,那就不好意思了,我这个系统接受不了.通过限流算法,我们可以控制系统的 qps,更好地对系统 ...

  2. Java 实现滑动时间窗口限流算法,你见过吗?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | dijia478 来源 | https://w ...

  3. 搞懂限流算法这一篇就够了

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 TL;DR(too long don't read) 限流算法:计 ...

  4. 搞懂限流算法这一篇就够了 No.154

    点击上方"方志朋",选择"设为星标" 做积极的人,而不是积极废人 TL;DR(too long don't read) 限流算法:计数器.滑动窗口.漏桶.令牌桶 ...

  5. Redis高并发限流策略之漏斗限流算法

    在双11活动当天凌晨,打折活动开始前多少名客户下单可以半折甚至是免单优惠,客户当然不会放过这个一年一次的机会,疯狂开始.这时候我们程序员小哥哥就苦了,稍一个不注意,服务器驾崩了,次日头条见.那么为了防 ...

  6. 接口限流算法:漏桶算法令牌桶算法

    工作中对外提供的API 接口设计都要考虑限流,如果不考虑限流,会成系统的连锁反应,轻者响应缓慢,重者系统宕机,整个业务线崩溃,如何应对这种情况呢,我们可以对请求进行引流或者直接拒绝等操作,保持系统的可 ...

  7. 接口限流算法:漏桶算法amp;令牌桶算法

    转载自 接口限流算法:漏桶算法&令牌桶算法 背景 每一个对外提供的API接口都是需要做流量控制的,不然会导致系统直接崩溃.很简单的例子,和保险丝的原理一样,如果用电符合超载就会烧断保险丝断掉电 ...

  8. 滑动窗口限流 java_Spring Boot 的接口限流算法优缺点深度分析

    点击上方蓝色字体,选择"标星公众号" 优质文章,第一时间送达 上一篇:这300G的Java资料是我师傅当年给我的,免费分享给大家(已修复) 下一篇:昨天分享资料不小心把百度网盘深处 ...

  9. 服务高可用利器——限流算法介绍与示例

    文章目录 0.前言 1.计数器 1.1 简介 1.2 示例 2.滑动窗口 2.1 简介 2.2 示例 3.漏桶 3.1 简介 3.2 示例 4.令牌桶 4.1 简介 4.2 示例 5.小结 参考文献 ...

最新文章

  1. 32岁封神!苏炳添博士重磅论文:我怎么跑这么快?
  2. 计算机产业深度报告:云计算与人工智能开启新一轮技术变革周期
  3. 还在自建代码仓库?阿里云的这款企业级代码管理工具免费、还香!
  4. 【渝粤题库】国家开放大学2021春3935理工英语2题目
  5. Mac OS 被XCode搞到无法正常开机怎么办?
  6. Markdown案例
  7. SQL Server-外部联接基础
  8. 【二叉树】牛客网:二叉树的镜像
  9. 软件基本功:以视频通话为例,交叉测试表格
  10. 滴答乐园一直显示连接服务器,滴答乐园iOS版-滴答乐园快手互粉2020苹果iOS版预约 v1.0-优盘手机站...
  11. Javascript_备忘录1
  12. 基于Spark的公安大数据实时运维技术实践
  13. Codeforces - Garland
  14. 浅析分布式数据库同步技术理论
  15. ios打包ipa 命令安装ipa到iphone
  16. WeWork中国首个创新项目即将落地武汉;中联重科新材料总部将落户湘阴 | 美通企业日报...
  17. ListView和条目点击事件、条目长按事件
  18. 车牌识别之一:车牌定位
  19. 中午不知道吃啥子?今天来写一个随机菜谱
  20. 【Web】一种好用的浏览器页面打印(打印销售小票)方法

热门文章

  1. elemet-ui后台表格自动排序解决办法
  2. Linux Tomcat JSP 布署详细教程
  3. java摇号_java语言实现一个摇号系统,但是可以内部设定中奖名单这个怎么实现?...
  4. No mapping for POST
  5. linux内核版本信息说明
  6. 土是独体字结构吗_“之”是独体字吗?还是上体结构的合体字?
  7. 我改回iPhone的13个理由
  8. 【多传感器融合理论】03多传感器信息融合理论(上)
  9. linux下载大文件失败,Linux下FTP/SFTP传输大文件总是失败的处理办法
  10. opencv图像处理—项目实战:答题卡识别判卷