背景
总所周知服务器的承受能力是有限度的,当请求量超过承受量时,可能会导致服务器宕机,连原本能解决的请求都无法处理.就比如地铁,本来每分钟能运输一万人,但是突然有十万人一起涌进地铁站,导致站台过于拥挤,人们都无法顺利上车,结果就是连原本排在最前面的一万人都无法上地铁,这个时候就可以采取限流策略,限制进站人数,让地铁能顺利的将一批批的乘客送走.
分层限流
- 限流总并发/连接/请求数
- 限制总资源数
- 限流某个接口的总并发/请求数
- 限流某个接口的时间窗请求数
- 平滑限流某个接口的请求数:前面几个限流都不能很好的应对突发请求,即瞬间请求可能都被允许,从而导致一些问题(极限值请求),因此在一些场景中需要对突发请求进行整形,整形为平均速率请求处理(比如5r/s,则每隔200ms处理一个请求,平滑了速率)。
限流策略
限流策略主要有两大类共四种
- 计数器限流
- 固定窗口
- 滑动窗口
- 桶限流
- 令牌桶
- 漏桶
计数器固定窗口
最容易想到的就是限制单位时间内的访问人数,当有一条请求进来时,计算器加1,当计数器数字到达阈值时,拒绝后续的请求.当计数周期达到后,计数器清零.比如QPS为10的话,可以使用秒级时间戳加上redis的inc实现,设置过期时间为1s.
但是有一个明显的弊端,就是固定窗口计数器算法无法处理突刺流量,比如10QPS,1ms中来了10个请求,后续的999ms的所有请求都会被拒绝。并且这1ms来了10个请求,服务器可能也无法并发的处理.
计数器滑动窗口
为了解决这个问题,可以将窗口再细分,比如使用百毫秒级时间戳,既每100ms处理一个请求.同100ms为单位的时间内如果请求数量超过限制,也会触发限流处理策略.然后统计的时间范围随着时间的推移同步后移。
即便滑动时间窗口限流算法可以保证任意时间窗口内接口请求次数都不会超过最大限流值,但是仍然不能防止在细时间粒度上面访问过于集中的问题。
为了应对上面的问题,对于时间窗口限流算法,还有很多改进版本,比如:
多层次限流,我们可以对同一个接口设置多条限流规则,除了 1 秒不超过 100 次之外,我们还可以设置 100ms 不超过 20 次 (代码中实现写了平均的两倍),两条规则同时限制,流量会更加平滑。
漏桶算法
漏桶算法就是服务器处理速度恒定,多余的请求可以被存储到水桶里,等待慢慢的流出,但是如果超过水桶的容纳极限,就会溢出,触发限流后续策略.
可以使用队列加固定数量消费者的方式来实现,请求都先发送到队列服务器中,让消费者去消费,如果队列中的任务堆积超过一定数量,可以”抛弃”请求,或者增加消费者数量.
令牌桶算法
其实就是池化思想,服务器的连接串数量固定,每次请求进来时会从连接串里拿连接串,当用完时释放连接串,这个时候连接串又回到了连接池里.当连接池里的串都被用完时,后续进来的请求只能等待或者被丢弃.
当触发了限流策略之后
无论是使用哪种算法,都能可能超过当前限流的最大值,这个时候要怎么处理呢?
要根据业务场景具体分析,比如是获取用户头像这种不影响主业务的功能,完全可以丢弃,或者返回指定的错误码,让客户端过一段时间再重试.如果是客户端上报购买数据这种重要数据,可以将请求放到延迟队列中,过一段时间后再尝试消费.
总结
四种算法都有各自适用的场景,要根据业务情况具体分析.有可能是为了防止恶意请求,也可能是服务器处理不过来如此大的压力.如果是服务器无法处理这么大的流量时,还可以通过降级,甚至熔断来保护服务器