近期处理了关于限流相关的问题,了解到有四种限流的算法,以下的代码使用的是Redis缓存,将此次的使用进行一个记录,以下是相关的代码:
限流的方式
1.固定窗口算法(计数器)
介绍:
- 固定窗口又称固定窗口(又称计数器算法,Fixed Window)限流算法,是最简单的限流算法,通过在单位时间内维护的计数器来控制该时间单位内的最大访问量。
优点:
- 固定窗口算法非常简单,易于实现和理解
缺点:
- 存在明显的临界问题,当两个窗口交界处,瞬时流量可能为2n,如图所示:限流阀值为2个请求,单位时间窗口是1s,在单位时间内的前0.7-1s和1-1.4s,
- 分别并发2个请求,虽然在时间窗口期间都没有超过阀值,但是如果算0.7-1.4s,则并发数为4个,已经超过单位时间1s不超过2阀值的定义
原理图:
产生的问题:
固定窗口算法代码:
1 | /** |
2.滑动窗口算法
介绍:
- 为了防止瞬时流量,可以把固定窗口近一步划分成多个格子,每次向后移动一小格,而不是固定窗口大小,可以解决固定窗口临界值的问题,但是无法避免。这就是滑动窗口(Sliding Window)。
优点:
- 可以消除计数器算法跨周期时的流量高峰。周期内的间隔划分越多,流量越平稳。
缺点:
- 无法缓存多余的请求,需要合理调整时间窗口大小,流量的数量达到阈值时会瞬间掐断流量,会导致流量不够平滑.
原理图:
第一次的请求发起时间为:14:30:00 窗口时间为:14:30:00-14:30:02
第二次的请求发起时间为:14:30:01 窗口时间为:14:30:00-14:30:02
第三次的请求发起时间为:14:30:03 窗口时间为:14:30:02-14:30:04
第四次的请求发起时间为:14:30:05 窗口时间为:14:30:04-14:30:06
滑动窗口相比较固定窗口是如何减少临界值的影响:
从图上可以看到时间创建是一种向右滑动的方式前进, 滑动窗口限流能够显著减少临界问题产生的影响,但是不能消除掉。
窗口每次向前滑动一个单元格,当请求到达时,只要窗口中所有单元格的计数总和不超过阈值都可以放行, 假如2s秒限流4个 那么就是也就是滑动窗口(单位时间)被划分为4个小周期。每格表示0.5S。每过0.5s时间窗口就会往右滑动一格。而且每个小周期,都有自己独立的计数器
具体的示例以及固定窗口和滑动窗口的区别:
14:30:00-14:30:02
滑动窗口一:0-0.5
滑动窗口二0.5-1
滑动窗口三:1-1.5
滑动窗口四:1.5-2
此时分别在1.8s来了5个请求,滑动窗口三已经限流,2.6s又来了5个请求,如果是固定算法的话会不限流,
但是过了1s会向右移动两个周期,当前的时间窗口单位2.5-3s,2.6处于这个时间段之内,这个区域的数量已经超过限定.就会触发限流。
滑动算法的周期划分越多,那么滚动的就会越平滑,限流的统计数量则越准确;
问题:
但是临界值的问题无法消除,只是缩小了范围而已;
滑动窗口算法代码:
1 | public boolean slidingWindowRateState(RateLimiter rateLimiter,String combineKey){ |
3.漏桶算法
介绍:
- 漏桶限流算法(Leaky Bucket Algorithm)是一种流量控制算法,用于控制流入网络的数据速率,以防止网络拥塞。它的思想是将数据包看作是水滴,漏桶看作是一个固定容量的水桶,数据包像水滴一样从桶的顶部流入桶中,并通过桶底的一个小孔以一定的速度流出,从而限制了数据包的流量。
优点:
- 令牌桶算法是一种流量控制机制,非常适合于处理突发流量,同时保证一定程度的平滑流动。它的工作原理类似于一个实际的水桶,其中水桶代表令牌桶,水流代表令牌。令牌以恒定的速率填充到桶中,直到达到桶的容量上限,多余的> 令牌会被丢弃。
- 可以平滑限制请求的处理速度,避免瞬间请求过多导致系统崩溃或者雪崩。
- 可以控制请求的处理速度,使得系统可以适应不同的流量需求,避免过载或者过度闲置。
- 可以通过调整桶的大小和漏出速率来满足不同的限流需求,可以灵活地适应不同的场景。
缺点:
- 短时间内有突发的大流量时,漏桶可能处理不了这些请求,还有就是同时放行多个请求的时候会导致业务处理压力比较大。
原理图:
问题:
因为速率是固定的无法处理瞬时利流量,不能很快处理请求
漏桶算法代码:
1 | /** |
4.令牌桶算法
介绍:
- 令牌桶算法是一种常用的限流算法,可以用于限制单位时间内请求的数量。该算法维护一个固定容量的令牌桶,每秒钟会向令牌桶中放入一定数量的令牌。当有请求到来时,如果令牌桶中有足够的令牌,则请求被允许通过并从令牌桶中消耗一个令牌,否则请求被拒绝。
优点:
- 稳定性高:令牌桶算法可以控制请求的处理速度,可以使系统的负载变得稳定。
- 精度高:令牌桶算法可以根据实际情况动态调整生成令牌的速率,可以实现较高精度的限流。
- 弹性好:令牌桶算法可以处理突发流量,可以在短时间内提供更多的处理能力,以处理突发流量。
缺点:
- 实现复杂:令牌桶算法的实现较为复杂。对短时请求难以处理:在短时间内有大量请求到来时,可能会导致令牌桶中的令牌被快速消耗完,从而限流。这种情况下,可以考虑使用漏桶算法。
- 时间精度要求高:令牌桶算法要求时间精度较高,因为是根据时间来生成令牌的数量,如果系统时间出现错误,会导致限流效果不理想。
原理图:
令牌桶算法代码:
1 | /** |
公共的参数
RateLimiterAspect.java
1 | /** |
RateLimiterData.java
1 | /** |
RateLimiter.java
1 | @Target(ElementType.METHOD) |
结束
- 固定窗口算法:实现简单,性能高,有明显的临界问题,瞬时流量可达到阈值的2倍。
- 滑动窗口算法:消除了临界问题,但是当流量到达阈值时会瞬间掐断流量,导致流量不够平滑。
- 漏桶算法:想要达到限流的目的,又不会掐断流量,使得流量更加平滑,但是因为是固定速率流出水,无法处理瞬时利流量,不能很快处理请求。
- 令牌算法:处理突发流量,同时保证一定程度的平滑流动。
此次以上就是限流的四种算法,具体使用那种算法需要根据自身的业务进行选择,如果是分布式的话推荐使用redis+Lua脚本来实现市面上也有其他的限流工具,
如:基于令牌桶实现的限流组件Guava,基于滑动窗口实现的分布式服务架构的流量控制框架Sentinel。
Lua脚本
令牌算法的脚本
1 | -- 令牌总数 |