服务限流常见解决方案

一、背景

为了保证系统稳定性和可用性,我们有时候需要拒绝一些超出承受范围的请求;

服务限流就是其中的一种方案:限定请求的速率。

二、常见的限流算法

固定窗口计数器

原理

原理是将时间划分为固定大小的窗口,在每个窗口内限制请求的数量或速率,即固定窗口计数器算法规定了系统单位时间处理的请求数量。

假如我们规定系统中某个接口 1 分钟只能被访问 33 次的话,使用固定窗口计数器算法的实现思路如下:

  • 将时间划分固定大小窗口,这里是 1 分钟一个窗口。
  • 给定一个变量 counter 来记录当前接口处理的请求数量,初始值为 0(代表接口当前 1 分钟内还未处理请求)。
  • 1 分钟之内每处理一个请求之后就将 counter+1 ,当 counter=33 之后(也就是说在这 1 分钟内接口已经被访问 33 次的话),后续的请求就会被全部拒绝。
  • 等到 1 分钟结束后,将 counter 重置 0,重新开始计数。

固定窗口计数器

特点

  1. 实现简单
  2. 限流不够平滑,可能出现两种情况
    1. 某个接口限流 一分钟30次,如果前30s有30次请求,那后30s无法处理请求
    2. 某个接口限流 一分钟30次,如果第1s内有30次请求,可能会超出接口处理能力。

滑动窗口计数器

原理

以上面窗口为60s为例,可以将窗口进一步划分成60 * 1s的小窗口,那么当前窗口就是由60个小窗口组成,

每过一个时间周期(1s)小窗口滑动一次,按照整个大窗口的请求数量来计算是否超出阈值。

特点

  1. 相比于固定窗口,滑动窗口可以应对激增流量
  2. 相比于固定窗口,可以提供更精确的限流控制
  3. 仍然出现限流不够平滑的问题(1)

漏桶

原理

类似于消息队列,服务以固定速率从队列中拿请求。

特点

  1. 实现简单
  2. 可以控制限流速率
  3. 无法应对激增流量
  4. 队列等待问题

令牌桶

原理

请求在被处理之前拿到令牌,请求完毕后将令牌丢弃。

按照一定速率向桶里添加令牌,如果装满了,就不能继续添加令牌。

特点

  1. 可以限制平均速率和应对激增流量
  2. 可以动态调整生成令牌的速率

三、单机限流

Guava RateLimiter

特点

基于令牌桶;开箱即用。

1
2
3
4
5
6
Guava 地址:https://github.com/google/guava
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>

Demo

1
2
3
4
5
6
7
8
9
10
11
public class RateLimiterDemo {

public static void main(String[] args) {
// 1s 放 5 个令牌到桶里也就是 0.2s 放 1个令牌到桶里
RateLimiter rateLimiter = RateLimiter.create(5);
for (int i = 0; i < 10; i++) {
double sleepingTime = rateLimiter.acquire(1);
System.out.printf("get 1 tokens: %ss%n", sleepingTime);
}
}
}

Resilience4j

特点

轻量级容错组件,提供限流、熔断、负载保护、自动重试等保障系统高可用的功能;

生态好,很多网关使用Resilience4j来做限流熔断;

绝大多数场景都适合;(Spring官方推荐,SpringCloud Gateway使用

1
2
地址:https://github.com/resilience4j/resilience4j
中文介绍:https://github.com/lmhmhl/Resilience4j-Guides-Chinese/blob/main/getting-start/Introduction.md

四、分布式限流

常见方案:

  1. 借助中间件:借助Sentinel 或者使用Redis来实现
  2. 网关层限流:通常网关层也需要借助中间件/框架。

基于Redis的限流脚本,https://github.com/apache/shenyu

五、总结

限流是一种常见的系统保护方案(有损),通过拒绝掉部分请求保证大多数请求的正常响应。

常见的限流算法有四种

  1. 固定窗口计数器
  2. 滑动窗口计数器
  3. 漏桶算法
  4. 令牌桶算法

其中,比较常用的是滑动窗口计数器和令牌桶算法

单机场景下的限流,可以借助Guava组件或者Resilience4j来实现

分布式场景下,就需要借助中间件的帮助,常见方案是Redis+Lua脚本。

补充一句,我司常用的实现,是将集群限流按照机器数分摊到各个机器,最终只需要实现单机限流。


服务限流常见解决方案
https://yzaf.top/2024/rate-limiter/
作者
why
发布于
2024年4月20日
许可协议