本文介绍了四种常见的限流算法及其实现方式。固定窗口限流简单但存在临界突刺问题;滑动窗口通过时间片滑动解决突刺问题,但滑动单位选择困难;漏桶算法以固定速率处理请求,能削峰缓冲但不够灵活;令牌桶算法允许突发流量,并发性能更好但时间单位选择仍需考量。实现层面,单机限流可使用 Guava 的 RateLimiter,分布式限流推荐 Redisson 或网关层工具如 Sentinel。提供了 Redisson 的配置示例和两种限流设置方式,建议采用基于 Duration 的新 API 实现更简洁的限流控制。
限流算法介绍
1、固定窗口限流
在单位时间内允许部分操作。
比如,单位时间(窗口)为 1 小时,一小时内只能有 100 个用户能访问,如果在一小时内就已经达到了次数上限 100,那么这时不论来多少请求都会被拒绝掉,也就是说 1 小时内设定了多少次就是只能有多少次,只有到 1 小时才会重置次数。但是它有一个突刺问题,例如在前 59 分钟都没有一个用户进行访问,在 59 分钟的时候 100 个用户同时访问,然后 1 小时 01 分时候又有 100 个用户都访问了,那就是 2 分钟有 200 个用户同时访问,可能瞬间通过 200 个请求,在两个窗口的临界突刺,服务器可能就会有流量高峰危险。
优点:实现简单
缺点:突刺问题
2、滑动窗口限流
在单位时间内允许部分操作,不过这个单位时间是滑动的,需要指定一个滑动时间单位(时间片)。
**滑动窗口解决了固定窗口的突刺问题。**滑动窗口是持续滑动,逐时间片淘汰旧数据的过程,它不会像固定窗口一样在固定的时间点重置次数,而是持续淘汰旧数据,因此能更精确地控制任意连续内的请求速率。
比如,单位时间(窗口)是 1 小时,指定滑动单位是 1 分钟,那么在 59 分钟时它的限流窗口是 59 分1 小时 59 分,经过 2 分钟它的限流窗口就是 1 小时 01 分2 小时 01 分,这个时间段还是就只接收 100 个请求,如果有超过第 100 个的请求访问,超过的访问请求都会被拒绝掉;如果每 2 分钟都有 1 个旧的请求数据淘汰,经过 2 分钟这时只有 99 个数据就可以允许有 1 个新的请求进入。
优点:解决突刺问题
缺点:滑动窗口的限流效果与滑动单位有关,滑动单位小效果好,不过往往很难选到一个合适的滑动单位
3、漏桶算法限流(推荐)
请求桶大小固定,以固定的速率处理请求,当请求桶里面满了之后会拒绝请求。
漏桶算法限流的基本原理为:水(对应请求)从进水口进入到漏桶里,漏桶以一定的速度出水(请求放行),当水流入速度过大,桶内的总水量大于桶容量会直接溢出,请求被拒绝。
大致的漏桶限流规则如下:
- 进水口(对应客户端请求)以任意速率流入进入漏桶。
- 漏桶的容量是固定的,出水(放行)速率也是固定的。
- 漏桶容量是不变的,如果处理速度太慢,桶内水量会超出了桶的容量,则后面流入的水滴会溢出,表示请求拒绝。
以一定速率处理请求,有大量流量进入时,会发生溢出,从而限流保护服务可用(也就是削峰),不至于直接请求到服务器,缓冲压力(也就是缓冲)。
优点:一定程度上应对流量突刺,以恒定的速率处理请求,保证服务器安全
缺点:漏桶出口的速度固定,不能灵活的应对后端能力提升。比如,通过动态扩容,后端流量从 100QPS 提升到 1000QPS,漏桶没有办法。
4、令牌桶算法限流(推荐)
管理员提前生成一批令牌,比如每秒 10 个令牌,当用户要操作前,先去拿到一个令牌,有令牌的人就有资格执行操作、能同时执行操作,而拿不到令牌的就拒绝请求得等着。当然了令牌的数量也是有上限的,令牌的数量与时间和发放速率强相关,时间流逝的时间越长,会不断往桶里加入越多的令牌,如果令牌发放的速度比申请速度快,令牌桶会放满令牌,直到令牌占满整个令牌桶。
令牌桶的大小固定,令牌的产生速度固定可以自己设置,但是消耗令牌(即请求)速度不固定(可以应对一些某些时间请求过多的情况);每个请求都会从令牌桶中取出令牌,如果没有令牌则丢弃该次请求。
令牌桶限流大致的规则如下:
- 进水口按照某个速度,向桶中放入令牌。
- 令牌的容量是固定的,但是放行的速度不是固定的,只要桶中还有剩余令牌,一旦请求过来就能申请成功,然后放行。
- 如果令牌的发放速度,慢于请求到来速度,桶内就无牌可领,请求就会被拒绝。
优点:比漏桶更高效,可以方便地应对突发出口流量,能够并发处理同时的请求,并发性能会更高
缺点:还是时间单位选取的问题,令牌的产生速度、消耗令牌的速度对应时间选多少合适


