请求限流总结

Posted by     "zengchengjie" on Friday, January 21, 2022

概念

通常我们说的限流指代的是 限制到达系统的并发请求数,使得系统能够正常的处理 部分 用户的请求,来保证系统的稳定性。

限流不可避免的会造成用户的请求变慢或者被拒的情况,从而会影响用户体验。因此限流是需要在用户体验和系统稳定性之间做平衡的,即我们常说的 trade off

日常的业务上有类似秒杀活动、双十一大促或者突发新闻等场景,用户的流量突增,后端服务的处理能力是有限的,如果不能处理好突发流量,后端服务很容易就被打垮。

亦或是爬虫等不正常流量,我们对外暴露的服务都要以最大恶意去防备我们的调用者。我们不清楚调用者会如何调用我们的服务。假设某个调用者开几十个线程一天二十四小时疯狂调用你的服务,不做啥处理咱服务也算完了。更胜的还有DDos攻击。

限流算法

场景:假设一个银行网点,有多个业务人员,工作人员一天工作8小时,一般的情况下,一个小时处理25个业务,一天最多处理200个客户的业务。

目标:我们要制定一种规则,只要每天来的人是200个,我可以完成客户的业务。

侧面规则:每天的平均人流量在<=50/h的前提下,能够在一天内正常完成业务

  • 计数器限流

    暴力做法:门口保安直接说银行一天只能处理200个,多的人明天再来。

    缺点:然后闲了一天没人来,快下班了,来了200个,200个人蜂拥而上,显然是不现实的, 此时业务无法完成。

  • 固定窗口限流

    门口保安每个小时统计一次人数,如果超过25个人,不让进

    缺点:有这个规定后,人流量均匀的进来了,但是在不违反规则的情况下,如果在15:50的时候来了20个人,16:10的时候来了20个,那么银行在某种意义上也是处理不了这过多的业务,无法完成

  • 滑动窗口限流

    每10分钟往前滑动一个时间窗口,在这个时间窗口内限制人数,比如,15:00-16:00是限制25个,那么15:10-16:10之间也是限制25个,如果15:55有20人来处理业务,在16:05来了20人,门口保安可以和超过的10人说处理失败,下个时间窗口再来。

    缺点:其实滑动时间窗口和固定时间窗口一样,都是基于时间窗口处理的,所以永远有时间窗口临界值的问题:处理不了突发流量。

    例如:15:55来了20人,那么到16:11的时候来了20人,这两拨人不在一个时间窗口,理论上没有违反规则,那么依然无法完成业务。

  • 漏桶算法

    解决办法:排队。

    假设5个柜员,让每个人来的人排队,所有柜员每隔12分钟办1个人的业务。这样,对于一会儿来20个一会儿来30个的情况,我就能保证每个人都有机会按顺序处理业务。

    缺点:无法解决突发流量问题,如果一下来了300个,那最后那100个可能等了很久,被告知今天下班了。。

  • 令牌桶算法

    取号排队,解决问题,每天200个号,来的人先取号,等着,如果超过200个没取到号,就被告知满了,明天再来。

总结

其实各个算法有个的优缺点,并不是桶算法就更好,比如令牌桶,需要维护好令牌(提前预热token令牌),如果没有预热,或者来不及放入令牌,则系统空闲,确误拒绝了请求,浪费了系统资源。

其次,就是等待时间过长。

所以漏桶和令牌桶其实比较适合阻塞式限流场景,即没令牌我就等着,这就不会误杀了,而漏桶本就是等着。比较适合后台任务类的限流。而基于时间窗口的限流比较适合对时间敏感的场景,请求过不了你就快点儿告诉我。

限流的难点

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

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

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

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

其实真实的业务场景很复杂,需要限流的条件和资源很多,每个资源限流要求还不一样。

限流组件

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

java

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

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

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

golang

Golang 官方提供的扩展库里就自带了限流算法的实现,即 golang.org/x/time/rate。该限流器也是基于 Token Bucket(令牌桶) 实现的。

Nginx限流