redis 限流策略
固定时间窗口算法
- 将某一个时间段当做一个窗口,在这个窗口内存在一个计数器记录这个窗口接收请求的次数,每接收一次请求便让这个计数器的值加一,如果计数器的值大于请求阈值的时候,即开始限流。当这个时间段结束后,会初始化窗口的计数器数据,相当于重新开了一个窗口重新监控请求次数
优点
在固定的时间内出现流量溢出可以立即做出限流。每个时间窗口不会相互影响
在时间单元内保障系统的稳定。保障的时间单元内系统的吞吐量上限
缺点
正如图示一样,他的最大问题就是临界状态。在临界状态最坏情况会受到两倍流量请求
除了临界的情况,还有一种是在一个单元时间窗内前期如果很快的消耗完请求阈值。那么剩下的时间将会无法请求。这样就会因为一瞬间的流量导致一段时间内系统不可用。这在互联网高可用的系统中是不能接受的。
滑动时间窗口算法
- 滑动窗口为固定窗口的改良版,解决了固定窗口在窗口切换时会受到两倍于阈值数量的请求,滑动窗口在固定窗口的基础上,将一个窗口分为若干个等份的小窗口,每个小窗口对应不同的时间点,拥有独立的计数器,当请求的时间点大于当前窗口的最大时间点时,则将窗口向前平移一个小窗口(将第一个小窗口的数据舍弃,第二个小窗口变成第一个小窗口,当前请求放在最后一个小窗口),整个窗口的所有请求数相加不能大于阀值。
优点
实质上就是固定时间窗口算法的改进。所以固定时间窗口的缺点就是他的优点。
内部抽象一个滑动的时间窗,将时间更加小化。存在边界的问题更加小。客户感知更弱了。
缺点
不管是固定时间窗口算法还是滑动时间窗口算法,他们都是基于计数器算法进行优化,但是他们对待限流的策略太粗暴了。
为什么说粗暴呢,未限流他们正常放行。一旦达到限流后就会直接拒绝。这样我们会损失一部分请求。这对于一个产品来说不太友好
漏桶算法
- 滑动时间窗口虽然可以极大程度的规避临界值问题,但是始终还是避免不了
- 另外时间算法还有个致命的问题,他无法面对突如其来的大量流量,因为他在达到限流后直接就拒绝了其他额外流量
- 针对这个问题我们继续优化我们的限流算法。 漏桶算法应运而生
优点
面对限流更加的柔性,不在粗暴的拒绝
增加了接口的接收性,保证下流服务接收的稳定性。均匀下发
缺点
漏桶容量是个短板
令牌桶算法
- 令牌桶和漏桶法是一样的。只不过将桶的作用方向改变了一下,漏桶的出水速度是恒定的,如果流量突然增加的话我们就只能拒绝入池
- 令牌桶是将令牌放入桶中,我们知道正常情况下令牌就是一串字符当桶满了就拒绝令牌的入池,但是面对高流量的时候正常加上我们的超时时间就留下足够长的时间生产及消费令牌了。这样就尽可能的不会造成请求的拒绝。最后,不论是对于令牌桶拿不到令牌被拒绝,还是漏桶的水满了溢出,都是为了保证大部分流量的正常使用,而牺牲掉了少部分流量
- 令牌桶算法提及到输入速率和输出速率,当输出速率大于输入速率,那么就是超出流量限制了。每访问一次请求的时候,可以从Redis中获取一个令牌,如果拿到令牌了,那就说明没超出限制,而如果拿不到,则结果相反。依靠上述的思想,我们可以结合redis的List数据结构List的leftPop来获取令牌
// 输出令牌 public Response limitFlow2(Long id){ Object result = redisTemplate.opsForList().leftPop("limit_list"); if(result == null){ return Response.ok("当前令牌桶中无令牌"); } return Response.ok(articleDescription2); }
再依靠Java的定时任务,定时往List中rightPush令牌,当然令牌也需要唯一性,这里还是用UUID进行了生成
// 10S的速率往令牌桶中添加UUID,只为保证唯一性 @Scheduled(fixedDelay = 10_000,initialDelay = 0) public void setIntervalTimeTask(){ redisTemplate.opsForList().rightPush("limit_list",UUID.randomUUID().toString()); }
不积跬步,无以至千里;不积小流,无以成江海