主流的四种限流策略,我都可以通过 redis 实现

引言
在 web 开发中功能是基石,除了功能以外运维和防护就是重头戏了。因为在网站运行期间可能会因为突然的访问量导致业务异常、也有可能遭受别人恶意攻击
所以我们的接口需要对流量进行限制。俗称的 QPS 也是对流量的一种描述
针对限流现在大多应该是令牌桶算法,因为它能保证更多的吞吐量。除了令牌桶算法还有他的前身漏桶算法和简单的计数算法
下面我们来看看这四种算法
固定时间窗口算法
固定时间窗口算法也可以叫做简单计数算法。网上有很多都将计数算法单独抽离出来。但是笔者认为计数算法是一种思想,而固定时间窗口算法是他的一种实现
包括下面滑动时间窗口算法也是计数算法的一种实现。因为计数如果不和时间进行绑定的话那么失去了限流的本质了。就变成了拒绝了

优点
在固定的时间内出现流量溢出可以立即做出限流。每个时间窗口不会相互影响
在时间单元内保障系统的稳定。保障的时间单元内系统的吞吐量上限
缺点
正如图示一样,他的最大问题就是临界状态。在临界状态最坏情况会受到两倍流量请求
除了临界的情况,还有一种是在一个单元时间窗内前期如果很快地消耗完请求阈值。那么剩下的时间将会无法请求。这样就会因为一瞬间的流量导致一段时间内系统不可用。这在互联网高可用的系统中是不能接受的。
实现
好了,关于原理介绍及优缺点我们已经了解了。下面我们动手实现它
首先我们在实现这种计数时,采用 redis 是非常好的选择。这里我们通过 redis 实现
controller
service
结果测试

我们设置的 qps=3 , 我们可以看到五个并发进来后前三个正常访问,后面两个就失败了。稍等一段时间我们在并发访问,前三个又可以正常访问。说明到了下一个时间窗口

滑动时间窗口算法
针对固定时间窗口的缺点--临界值出现双倍流量问题。 我们的滑动时间窗口就产生了。
其实很好理解,就是针对固定时间窗口,将时间窗口统计从原来的固定间隔变成更加细度化的单元了。
在上面我们固定时间窗口演示中我们设置的时间单元是 1S 。 针对 1S 我们将 1S 拆成时间戳。
固定时间窗口是统计单元随着时间的推移不断向后进行。而滑动时间窗口是我们认为的想象出一个时间单元按照相对论的思想将时间固定,我们的抽象时间单元自己移动。抽象的时间单元比实际的时间单元更小。
读者可以看下下面的动图,就可以理解了。

优点
实质上就是固定时间窗口算法的改进。所以固定时间窗口的缺点就是他的优点。
内部抽象一个滑动的时间窗,将时间更加小化。存在边界的问题更加小。客户感知更弱了。
缺点
不管是固定时间窗口算法还是滑动时间窗口算法,他们都是基于计数器算法进行优化,但是他们对待限流的策略太粗暴了。
为什么说粗暴呢,未限流他们正常放行。一旦达到限流后就会直接拒绝。这样我们会损失一部分请求。这对于一个产品来说不太友好
实现
滑动时间窗口是将时间更加细化,上面我们是通过 redis#setnx 实现的。这里我们就无法通过他统一记录了。我们应该加上更小的时间单元存储到一个集合汇总。然后根据集合的总量计算限流。redis 的 zsett 数据结构就和符合我们的需求。
为什么选择 zset 呢,因为 redis 的 zset 中除了值以外还有一个权重。会根据这个权重进行排序。如果我们将我们的时间单元及时间戳作为我们的权重,那么我们获取统计的时候只需要按照一个时间戳范围就可以了。
因为 zset 内元素是唯一的,所以我们的值采用 uuid 或者雪花算法一类的 id 生成器
controller
service
结果测试

和固定时间窗口采用相同的并发。为什么上面也会出现临界状况呢。因为在代码里时间单元间隔比固定时间间隔采用还要大 。 上面演示固定时间窗口时间单元是 1S 出现了最坏情况。而滑动时间窗口设计上就应该间隔更短。而我设置成 10S 也没有出现坏的情况
这里就说明滑动比固定的优处了。如果我们调更小应该更加不会出现临界问题,不过说到底他还是避免不了临界出现的问题
漏桶算法
滑动时间窗口虽然可以极大程度地规避临界值问题,但是始终还是避免不了
另外时间算法还有个致命的问题,他无法面对突如其来的大量流量,因为他在达到限流后直接就拒绝了其他额外流量
针对这个问题我们继续优化我们的限流算法。 漏桶算法应运而生

优点
面对限流更加的柔性,不再粗暴的拒绝。
增加了接口的接收性
保证下流服务接收的稳定性。均匀下发
缺点
我觉得没有缺点。非要鸡蛋里挑骨头那我只能说漏桶容量是个短板
实现
controller
service
在 service 中我们通过 redis 的 list 的功能模拟出桶的效果。这里代码是实验室性质的。在真实使用中我们还需要考虑并发的问题
下游消费
测试
我们还是通过 50 并发循环 10 次访问。我们可以发现只有在一开始能达到比较高的吞吐量。在随后桶的容量满了之后。而下游水滴速率比上游请求速率慢的情况下。只能以下游恒定的速度接收访问。
他的问题也暴露得很明显。针对时间窗口的不足漏桶进行的不足,但是仍是不足。无法彻底避免请求溢出的问题。
请求溢出本身就是一种灾难性的问题。所有的算法目前都没有解决这个问题。只是在减缓他带来的问题

令牌桶算法
令牌桶和漏桶法是一样的。只不过将桶的作用方向改变了一下。
漏桶的出水速度是恒定的,如果流量突然增加的话我们就只能拒绝入池
但是令牌桶是将令牌放入桶中,我们知道正常情况下令牌就是一串字符当桶满了就拒绝令牌的入池,但是面对高流量的时候正常加上我们的超时时间就留下足够长的时间生产及消费令牌了。这样就尽可能地不会造成请求的拒绝
最后,不论是对于令牌桶拿不到令牌被拒绝,还是漏桶的水满了溢出,都是为了保证大部分流量的正常使用,而牺牲掉了少部分流量
版权声明: 本文为 InfoQ 作者【喝酸奶不舔盖】的原创文章。
原文链接:【http://xie.infoq.cn/article/2f2b28c7e02b931dafa5532ce】。未经作者许可,禁止转载。
评论