接口限流是一种用于控制和管理网络请求的策略,主要用于保护服务端资源,防止过多的请求同时涌入,导致系统过载或崩溃。接口限流可以对某个特定的接口、API 或服务设置最大的请求速率或并发请求数,确保系统在承受能力范围内运行,并提供更好的稳定性和可靠性。接口限流适用于如下场景:
- 保护服务器,避免突发流量导致服务不可以用:限制请求的数量,防止过多的请求同时到达服务器,避免服务器过载,保持系统的可用性和稳定性。尤其是在服务之间相互依赖的场景,如果一个服务负载过大,可能会导致依赖它的服务不可用或者响应时间过长。
- 提高系统性能:通过限制并发请求,可以有效地平滑请求流量,避免瞬时高峰带来的性能问题。
- 优化资源利用:限制频繁请求的客户端,使得服务器能更有效地利用资源响应其他请求,提高系统整体资源利用率。
- 防止恶意攻击(接口防刷):限流可以防止某些类型的恶意攻击,例如 DDoS(分布式拒绝服务攻击),让服务器有更多的机会来识别和应对这些攻击。
1.限流策略常用算法
在 Java 领域中提供了丰富的限流组件和工具,常见的限流组件和工具:
- Guava:Guava 是 Google 的一个 Java 工具类,提供了 RateLimiter 类来实现限流功能。
- Redis:Redis 是一种内存数据结构存储系统,也可以用于实现限流功能。通过 Redis 的计数器或令牌桶算法,可以很容易地实现接口限流。
- Spring Cloud Gateway 和 Netflix Zuul:这些是用于构建微服务网关的组件,它们通常也提供了限流功能,可用于对微服务的接口进行全局限流或路由级别限流。
- Sentinel:Alibaba 的 Sentinel 是一个功能丰富的流量控制组件,它支持丰富的流控规则配置、熔断降级等功能,适用于微服务场景下的限流和流量控制。
- Bucket4j:Bucket4j 是一个基于令牌桶算法的 Java 限流库,可以轻松实现基于令牌桶的限流策略。
- RateLimitJ:RateLimitJ 是一个简单易用的 Java 限流库,提供了令牌桶和漏桶算法的实现。
- Resilience4j:Resilience4j 是一个轻量级的容错库,提供了限流和熔断功能,适用于构建弹性的分布式系统。
- Nginx:作为一个高性能的反向代理服务器,Nginx 也可以用来实现接口限流,通过配置限速模块实现。
对于简单的限流需求,推荐使用 Guava 或其他基于内存的限流组件。对于复杂的微服务架构,则推荐 Sentinel、Resilience4j,另外 Redis、Nginx 限流也是非常常见的方案。所有的限流组件和工具都是基于固定窗口、滑动窗口、令牌桶、漏桶算法四种经典限流算法提供限流功能。
1.1 固定窗口算法
固定窗口算法(Fixed Window Rate Limiting Algorithm)是一种简单的计数器限流算法,它将时间划分为固定大小的时间窗口,在每个时间窗口内统计请求的数量,并与预设的阈值进行比较,从而判断是否需要限制请求。
固定窗口算法的实现步骤如下:
- 将时间分割成固定的时间窗口,例如每秒一个窗口。
- 在每个时间窗口内,记录进入系统的请求数量。
- 如果某个时间窗口内的请求数量超过了预设的阈值,就触发限流,拒绝多余的请求。
- 等待下一个时间窗口开始,清零请求数量,开始新的计数。并重复上述步骤。
固定窗口限流的伪代码实现如下:
// 统计请求数
public static Integer counter = 0;
// 最后许可请求时间
public static long lastAcquireTime = 0L;
// 固定时间窗口的时间,假设固定时间窗口是1000ms
public static final Long windowUnit = 1000L;
// 窗口阀值是10
public static final Integer threshold = 10;
public static boolean fixedWindowsTryAcquire() {
// 获取系统当前时间
long currentTime = System.currentTimeMillis();
// 检查是否在时间窗口内
if (currentTime - lastAcquireTime > windowUnit) {
// 计数器清0
counter = 0;
// 开启新的时间窗口
lastAcquireTime = currentTime;
}
// 小于阀值,计数统计器加1
if (counter < threshold) {
counter++;
return true;
}
return false;
}固定窗口算法虽然具有简单,易于实现和理解等优点,但是,它也存在一些问题,例如在时间窗口边界可能会出现突发流量,导致短时间内的请求被限流,同时在时间窗口结束时计数器的重置可能引起不必要的限流,无法处理短时间内的突发请求。固定窗口算法适用于一些简单的限流场景,对于更复杂的场景推荐使用更高级的限流算法来平滑地控制请求速率。
1.2 滑动窗口算法
滑动窗口算法是一种用于限流的算法,它在时间上将窗口分割为多个小窗口,并在每个小窗口内记录事件的发生次数。通过滑动这些小窗口,可以平滑地限制事件的发生频率。
1.3 令牌桶算法
令牌桶算法是一种用于流量控制的经典算法,可以帮助限制在一个时间窗口内的请求速率。它的基本原理是在令牌桶中定期生成令牌,每个令牌代表一个可以被处理的请求。当请求到来时,需要获取一个令牌才能被处理,否则请求将被延迟或拒绝。
1.4 漏桶算法
2.基于 Guava 实现限流
Guava 内部提供了 RateLimiter 类用于实现限流功能,其底层基于令牌算法,RateLimiter 常用方法如下:
- RateLimiter.create(double permitsPerSecond):静态工厂方法,用于创建一个新的限流器实例。permitsPerSecond 参数表示每秒产生的令牌数量,即限流器的速率。
- double acquire():获取一个令牌,并阻塞当前线程直到获取到令牌。该方法会根据限流器的速率进行令牌消耗,如果令牌桶中没有足够的令牌,则当前线程会被阻塞,直到获取到令牌。
- double acquire(int permits):获取指定数量的令牌,并阻塞当前线程直到获取到足够数量的令牌。与 acquire() 不同,该方法获取的是一次性的令牌数,而不是每次获取一个令牌。
- boolean tryAcquire():尝试非阻塞地获取一个令牌。该方法不会阻塞当前线程,如果令牌桶中有足够的令牌,则获取成功,返回 true;否则获取失败,返回 false。
- boolean tryAcquire(int permits):尝试非阻塞地获取指定数量的令牌。与 tryAcquire() 不同,该方法获取的是一次性的令牌数,而不是每次获取一个令牌。
- boolean tryAcquire(int permits, long timeout, TimeUnit unit):尝试在指定的时间范围内获取指定数量的令牌。该方法会在限定的时间内进行尝试,如果在指定的时间内成功获取到足够数量的令牌,则返回 true;如果在指定的时间内没有获取到足够数量的令牌,则返回 false。
- setRate(double permitsPerSecond):动态地修改限流器的速率。可以通过这个方法在运行时调整限流器的速率。
在 SpringBoot 中,集成 Guava Ratelimiter 主要分为自定义注解 AOP 和拦截器两种方式:
- 自定义注解+AOP 切面:自定义限流注解标识请求处理方法,通过 AOP 的方式对自定义限流注解进行切面。
- 拦截器:基于 MVC 框架的拦截器机制对目标请求进行拦截。
// guava依赖
implementation 'com.google.guava:guava:32.1.2-jre'
Java知识库