RateLimiter 是一种用于控制资源访问速率的工具,通常用于限制系统在单位时间内处理的请求数量,以防止系统过载或资源耗尽。RateLimiter 广泛应用于高并发系统、API 限流、流量控制等场景。
在 Java 中,RateLimiter 是 Google Guava 库 提供的一个实现。下面我们将详细讲解 RateLimiter 的原理、使用方法和应用场景。
1. RateLimiter 的核心概念
1.1 什么是 RateLimiter?
RateLimiter 是一种限流器,用于控制单位时间内允许的操作数量。它基于令牌桶算法(Token Bucket Algorithm)或漏桶算法(Leaky Bucket Algorithm)实现。
-
令牌桶算法:
- 系统以固定速率向桶中添加令牌。
- 每个请求需要消耗一个令牌。
- 如果桶中没有足够的令牌,请求将被限流。
-
漏桶算法:
- 请求以固定速率从桶中漏出。
- 如果桶已满,新的请求将被限流。
Guava 的 RateLimiter 是基于令牌桶算法实现的。
1.2 核心特性
-
平滑突发限制(Smooth Bursty):
- 允许短时间内突发请求,但长期平均速率不超过设定值。
-
平滑预热限制(Smooth Warming Up):
- 在系统启动时,逐步增加速率,避免冷启动时的流量冲击。
-
非阻塞式限流:
- 支持尝试获取令牌,如果令牌不足,可以选择等待或直接拒绝。
2. Guava RateLimiter 的使用
2.1 添加依赖
如果使用 Maven,需要在 pom.xml 中添加 Guava 依赖:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
运行 HTML
2.2 创建 RateLimiter
通过 RateLimiter.create() 方法创建一个限流器,指定每秒允许的请求数(QPS)。
import com.google.common.util.concurrent.RateLimiter;
public class RateLimiterExample {
public static void main(String[] args) {
// 创建一个每秒允许 2 个请求的限流器
RateLimiter rateLimiter = RateLimiter.create(2.0);
for (int i = 1; i <= 10; i++) {
// 尝试获取令牌
if (rateLimiter.tryAcquire()) {
System.out.println("处理请求: " + i);
} else {
System.out.println("请求被限流: " + i);
}
}
}
}
2.3 核心方法
1. RateLimiter.create(double permitsPerSecond)
- 创建一个平滑突发限制的限流器。
- 参数
permitsPerSecond表示每秒允许的请求数(QPS)。
2. RateLimiter.create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)
- 创建一个平滑预热限制的限流器。
- 参数
warmupPeriod表示预热时间。
3. rateLimiter.acquire()
- 阻塞当前线程,直到获取到令牌。
- 返回等待的时间(秒)。
4. rateLimiter.tryAcquire()
- 尝试获取令牌,如果令牌不足,立即返回
false。 - 不会阻塞线程。
5. rateLimiter.tryAcquire(int permits, long timeout, TimeUnit unit)
- 尝试在指定时间内获取指定数量的令牌。
- 如果超时仍未获取到令牌,返回
false。
2.4 示例代码
示例 1:平滑突发限制
import com.google.common.util.concurrent.RateLimiter;
public class SmoothBurstyExample {
public static void main(String[] args) {
// 每秒允许 2 个请求
RateLimiter rateLimiter = RateLimiter.create(2.0);
for (int i = 1; i <= 10; i++) {
// 获取令牌,如果令牌不足则阻塞
double waitTime = rateLimiter.acquire();
System.out.println("处理请求: " + i + ", 等待时间: " + waitTime + " 秒");
}
}
}
示例 2:平滑预热限制
import com.google.common.util.concurrent.RateLimiter;
import java.util.concurrent.TimeUnit;
public class SmoothWarmingUpExample {
public static void main(String[] args) {
// 每秒允许 2 个请求,预热时间为 3 秒
RateLimiter rateLimiter = RateLimiter.create(2.0, 3, TimeUnit.SECONDS);
for (int i = 1; i <= 10; i++) {
double waitTime = rateLimiter.acquire();
System.out.println("处理请求: " + i + ", 等待时间: " + waitTime + " 秒");
}
}
}
示例 3:非阻塞式限流
import com.google.common.util.concurrent.RateLimiter;
public class NonBlockingExample {
public static void main(String[] args) {
// 每秒允许 2 个请求
RateLimiter rateLimiter = RateLimiter.create(2.0);
for (int i = 1; i <= 10; i++) {
// 尝试获取令牌,如果令牌不足则直接返回
if (rateLimiter.tryAcquire()) {
System.out.println("处理请求: " + i);
} else {
System.out.println("请求被限流: " + i);
}
}
}
}
3. RateLimiter 的应用场景
3.1 API 限流
- 防止 API 被恶意请求或突发流量打垮。
- 例如,限制每个用户每秒只能调用某个 API 10 次。
3.2 资源保护
- 限制对数据库、文件系统等共享资源的访问速率,避免资源耗尽。
3.3 流量控制
- 在微服务架构中,限制服务之间的调用速率,防止雪崩效应。
3.4 任务调度
- 控制任务处理的速率,确保系统负载均衡。
4. RateLimiter 的底层原理
4.1 令牌桶算法
- 系统以固定速率向桶中添加令牌。
- 每个请求需要消耗一个令牌。
- 如果桶中没有足够的令牌,请求将被限流。
4.2 实现细节
- Guava 的
RateLimiter使用了一种称为**“存储桶”**的机制,动态调整令牌的生成速率。 - 支持突发流量(短时间内消耗多个令牌),但长期平均速率不超过设定值。
5. RateLimiter 的优缺点
优点
- 简单易用,集成方便。
- 支持突发流量和预热机制。
- 高性能,适合高并发场景。
缺点
- 单机限流,无法直接支持分布式限流。
- 需要根据实际场景调整参数,否则可能导致限流效果不理想。
6. 分布式限流
如果需要在分布式系统中实现限流,可以使用以下方案:
- Redis + Lua 脚本:通过 Redis 的原子操作实现分布式限流。
- Sentinel:阿里巴巴开源的分布式限流组件。
- Nginx 限流:通过 Nginx 的限流模块实现网关层限流。
7. 总结
RateLimiter是 Google Guava 提供的一个限流工具,基于令牌桶算法实现。- 支持平滑突发限制和平滑预热限制,适用于 API 限流、资源保护等场景。
- 在单机场景下性能优异,但在分布式场景中需要结合其他工具实现限流。
通过合理使用 RateLimiter,可以有效保护系统免受突发流量的冲击,提升系统的稳定性和可靠性。
8. 限制每个用户每秒只能调用某个 API 10 次。用 RateLimiter 实现
我们可以为每个用户创建一个独立的 RateLimiter 实例,并将其存储在缓存(如 ConcurrentHashMap)中,以便快速查找和更新。
以下是完整的实现代码:
import com.google.common.util.concurrent.RateLimiter;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class UserRateLimiter {
// 使用 ConcurrentHashMap 存储每个用户的 RateLimiter
private static final ConcurrentHashMap<String, RateLimiter> userRateLimiters = new ConcurrentHashMap<>();
// 每秒允许的请求数
private static final double PERMITS_PER_SECOND = 10.0;
/**
* 检查用户是否允许调用 API
*
* @param userId 用户 ID
* @return true 表示允许调用,false 表示限流
*/
public static boolean allowRequest(String userId) {
// 获取用户的 RateLimiter,如果不存在则创建一个新的
RateLimiter rateLimiter = userRateLimiters.computeIfAbsent(userId, k -> RateLimiter.create(PERMITS_PER_SECOND));
// 尝试获取令牌
return rateLimiter.tryAcquire();
}
/**
* 清理长时间未使用的 RateLimiter,避免内存泄漏
*/
public static void cleanUpInactiveUsers() {
userRateLimiters.entrySet().removeIf(entry -> {
// 如果 RateLimiter 长时间未被使用,则移除
return entry.getValue().getRate() == 0; // 这里可以根据实际需求设计更复杂的清理逻辑
});
}
public static void main(String[] args) throws InterruptedException {
// 模拟用户调用 API
String userId = "user123";
for (int i = 1; i <= 15; i++) {
boolean allowed = allowRequest(userId);
if (allowed) {
System.out.println("请求 " + i + ": 允许调用");
} else {
System.out.println("请求 " + i + ": 被限流");
}
// 模拟请求间隔
TimeUnit.MILLISECONDS.sleep(50);
}
// 清理不活跃的用户
cleanUpInactiveUsers();
}
}
优化建议
-
分布式限流:
- 如果系统是分布式的,单机的
RateLimiter无法满足需求。 - 可以使用 Redis 实现分布式限流,例如通过 Redis 的
INCR和EXPIRE命令。
- 如果系统是分布式的,单机的
-
清理策略:
- 需要定期清理不活跃用户的
RateLimiter,避免内存泄漏。 - 可以使用定时任务(如
ScheduledExecutorService)定期清理。
- 需要定期清理不活跃用户的
-
动态调整速率:
- 如果需要根据用户等级或业务需求动态调整速率,可以扩展
RateLimiter的创建逻辑。
- 如果需要根据用户等级或业务需求动态调整速率,可以扩展
-
限流提示:
- 当请求被限流时,可以返回提示信息(如 HTTP 429 Too Many Requests)。
分布式限流示例(基于 Redis)
如果需要实现分布式限流,可以使用 Redis 的 INCR 和 EXPIRE 命令。以下是一个简单的实现:
参考:rateLimiter + Caffeine 做限流 developer.jdcloud.com/article/310…