1. 前言
核心源码可参考:Google Guava RateLimiter
在当今的互联网应用中,高并发场景无处不在。无论是电商平台的秒杀活动、社交媒体的热点事件,还是API服务的突发流量,都可能对系统造成巨大压力。如果没有适当的保护措施,系统很容易被流量冲垮,导致服务不可用。
限流(Rate Limiting) 就是在这种背景下应运而生的一种关键技术。它就像交通信号灯一样,控制着请求的流量,确保系统在承受范围内稳定运行。而Google Guava库中的RateLimiter则是Java领域最常用的限流工具之一。
想象一下这样的场景:你的API服务突然因为某个网红推荐而流量暴增,如果没有限流,数据库连接可能被耗尽,CPU飙升至100%,最终导致整个服务崩溃。而有了RateLimiter,你可以平稳地处理这些流量,虽然可能会拒绝部分请求,但保证了核心服务的可用性。
本文主要介绍了:
- 突发限流和预热限流
- 限流的令牌方法实现
- Google Guava 内的关键源码
2. RateLimiter的核心概念
2.1 什么是限流?
限流本质上是一种流量控制策略,通过限制单位时间内的请求数量来保护系统。主要目标包括:
- 防止系统过载:避免突发流量打垮服务
- 保证公平性:确保每个用户或客户端公平使用资源
- 提升稳定性:在资源有限的情况下保证核心功能可用
2.2 RateLimiter的两种模式
Guava的RateLimiter提供了两种主要的限流算法:
2.2.1 平滑突发限流(SmoothBursty)
算法原理:令牌桶算法
令牌桶算法是RateLimiter的核心实现原理,其工作方式如下:
markdown
令牌桶示意图:
┌─────────────────┐
│ │
│ 令牌桶 │ 容量:burstSize
│ ●●●●●●●●●● │ 当前令牌数:storedPermits
│ │
└─────────────────┘
↓ 固定速率添加令牌
↓ (每秒钟添加permitsPerSecond个)
算法特点:
- 系统以固定速率向桶中添加令牌
- 桶有最大容量,防止无限累积
- 请求需要获取令牌才能被执行
- 如果桶中有足够令牌,请求立即执行(允许突发)
- 如果令牌不足,请求需要等待
使用示范:
java
// 创建平滑突发限流器
RateLimiter limiter = RateLimiter.create(10.0); // 每秒10个请求
// 算法参数说明:
// - 速率:10.0 permits per second
// - 最大突发容量:默认1秒的令牌量(10个)
// - 添加间隔:每100ms添加1个令牌
过程图示:
scss
时间轴和令牌变化:
时间(秒) 0.0 0.1 0.2 0.3 0.4 0.5
令牌数 10 9 8 7 6 5
请求 R1 R2 R3 R4 R5 R6
状态 立即 立即 立即 立即 立即 立即
突发请求场景:
时间(秒) 0.0 0.1 0.2
令牌数 10 0 0
请求 R1-R10
状态 全部立即执行,消耗10个令牌
代码示范:
java
public class SmoothBurstyDemo {
public void demonstrateBurstBehavior() {
RateLimiter limiter = RateLimiter.create(2.0); // 每秒2个请求
// 第一次请求:立即执行(桶中有初始令牌)
System.out.println("请求1等待时间: " + limiter.acquire() + "秒"); // 输出: 0.0
// 紧接着第二次请求:需要等待0.5秒(因为速率是2QPS,间隔0.5秒)
System.out.println("请求2等待时间: " + limiter.acquire() + "秒"); // 输出: ~0.5
// 等待1秒让桶中积累令牌
try { Thread.sleep(1000); } catch (InterruptedException e) {}
// 现在可以突发处理多个请求
System.out.println("请求3等待时间: " + limiter.acquire() + "秒"); // 输出: 0.0
System.out.println("请求4等待时间: " + limiter.acquire() + "秒"); // 输出: 0.0
System.out.println("请求5等待时间: " + limiter.acquire() + "秒"); // 输出: ~0.5
}
public void analyzeBurstCapacity() {
RateLimiter limiter = RateLimiter.create(5.0); // 每秒5个请求
// 突发容量测试
long start = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
limiter.acquire();
System.out.println("请求" + i + "执行时间: " +
(System.currentTimeMillis() - start) + "ms");
}
// 输出结果会显示前5个请求几乎同时执行,后5个请求按速率间隔执行
}
}
2.2.2 平滑预热限流(SmoothWarmingUp)
算法原理:带预热的令牌桶
注意:实际上Guava的预热算法并不是严格的线性,但我们可以用线性来近似理解。
平滑预热限流在令牌桶基础上增加了预热阶段,避免冷启动时系统突然承受高负载。
scss
预热过程示意图:
速率(QPS)
▲
10 │ ┌───────────── 稳定阶段(10 QPS)
│ /
│ /
│ / 预热阶段
│ / 速率从0逐渐上升到目标值
│ /
│ /
└────────┴───────▶ 时间(秒)
0 5
令牌添加速率变化:
时间(秒) 添加速率(QPS) 说明
0-1 2 初始低速
1-2 4 逐渐加速
2-3 6 继续加速
3-4 8 接近目标
4-5 10 达到稳定
5+ 10 稳定运行
算法特点:
- 冷启动保护:系统启动时从低速率开始
- 渐进式加速:速率随时间逐渐增加到最大值
- 避免过载:防止冷系统突然承受高负载
- 预热期配置:可以自定义预热时间
使用示范:
java
// 创建平滑预热限流器
RateLimiter limiter = RateLimiter.create(10.0, 5, TimeUnit.SECONDS);
// 参数说明:
// - 目标速率:10.0 permits per second
// - 预热时间:5秒
// - 预热策略:在5秒内从冷启动速率渐进达到10QPS
过程图示:
java
基于分段常数近似的计算:
时间段 速率 间隔 请求序列
[0,1) 2 QPS 0.5s R1(0.0), R2(0.5)
[1,2) 4 QPS 0.25s R3(1.0), R4(1.25)
[2,3) 6 QPS 0.167s R5(2.0), R6(2.167)
[3,4) 8 QPS 0.125s R7(3.0), R8(3.125)
[4,∞) 10 QPS 0.1s 后续请求...
代码示例:
java
public class SmoothWarmingUpDemo {
public void demonstrateWarmingUp() {
// 创建预热限流器:5秒内从冷启动达到10QPS
RateLimiter limiter = RateLimiter.create(10.0, 5, TimeUnit.SECONDS);
long startTime = System.currentTimeMillis();
// 在预热期间连续发起请求
for (int i = 0; i < 20; i++) {
double waitTime = limiter.acquire();
long currentTime = System.currentTimeMillis();
long elapsed = currentTime - startTime;
System.out.printf("请求%d: 等待%.3fs, 总耗时%.3fs%n",
i + 1, waitTime, elapsed / 1000.0);
}
// 观察输出:前几个请求等待时间较长,后续逐渐缩短
}
public void compareWithBursty() {
// 对比突发限流和预热限流
RateLimiter burstyLimiter = RateLimiter.create(10.0);
RateLimiter warmingUpLimiter = RateLimiter.create(10.0, 3, TimeUnit.SECONDS);
System.out.println("=== 突发限流器 ===");
testLimiter(burstyLimiter, "Bursty");
System.out.println("=== 预热限流器 ===");
testLimiter(warmingUpLimiter, "WarmingUp");
}
private void testLimiter(RateLimiter limiter, String name) {
long start = System.currentTimeMillis();
for (int i = 0; i < 5; i++) {
double waitTime = limiter.acquire();
long currentTime = System.currentTimeMillis();
System.out.printf("%s-请求%d: 等待%.3fs, 距离开始%.3fs%n",
name, i + 1, waitTime, (currentTime - start) / 1000.0);
}
}
}
3. 常用方法详解
3.1 创建限流器
3.1.1 方法签名
java
// 创建平滑突发限流器
public static RateLimiter create(double permitsPerSecond)
// 创建平滑预热限流器
public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)
3.1.2 应用示范
java
public class RateLimiterCreationDemo {
// 场景1:API接口限流 - 每秒最多100个请求
public static class ApiRateLimiter {
private static final RateLimiter apiLimiter = RateLimiter.create(100.0);
public void processApiRequest(Request request) {
if (apiLimiter.tryAcquire()) {
// 处理请求
handleRequest(request);
} else {
// 返回限流响应
throw new RateLimitExceededException("API rate limit exceeded");
}
}
}
// 场景2:数据库操作限流 - 预热模式,避免冷启动压力
public static class DatabaseRateLimiter {
private static final RateLimiter dbLimiter =
RateLimiter.create(50.0, 2, TimeUnit.SECONDS); // 2秒内从0逐步达到50QPS
public void executeQuery(String sql) {
double waitTime = dbLimiter.acquire(); // 可能需要等待
Connection conn = getConnection();
try {
// 执行数据库操作
executeSql(conn, sql);
} finally {
releaseConnection(conn);
}
}
}
// 场景3:外部服务调用限流
public static class ExternalServiceLimiter {
private static final RateLimiter externalLimiter = RateLimiter.create(20.0);
private static final int TIMEOUT_MS = 500;
public Response callExternalService(Request request) {
// 非阻塞方式,500ms内获取不到令牌则快速失败
if (externalLimiter.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
return httpClient.execute(request);
} else {
throw new ServiceTimeoutException("Rate limit timeout");
}
}
}
}
3.1.3 关键源码
java
// Guava RateLimiter的创建逻辑(简化版)
// 这里调用了重载的create方法,并传入一个SleepingStopwatch实例。SleepingStopwatch是一个用于测量时间的工具类,它使用系统计时器。
public static RateLimiter create(double permitsPerSecond) {
return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
}
// 这个方法允许指定预热时间和时间单位,同时还传入一个固定值3.0(这个值是冷启动因子,我们后面会解释)。同样,也使用了SleepingStopwatch。
public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) {
return create(permitsPerSecond, warmupPeriod, unit, 3.0, SleepingStopwatch.createFromSystemTimer());
}
上述两个方法最终都会调用一个私有的create方法,我们来看一下这个内部方法:
java
private static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
// 创建的是SmoothBursty实例
RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
rateLimiter.setRate(permitsPerSecond);
return rateLimiter;
}
private static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit, double coldFactor, SleepingStopwatch stopwatch) {
// 创建的是SmoothWarmingUp实例
RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit, coldFactor);
rateLimiter.setRate(permitsPerSecond);
return rateLimiter;
}
SmoothBursty和SmoothWarmingUp都是SmoothRateLimiter的子类,而SmoothRateLimiter又是RateLimiter的子类。
-
SmoothBursty的实现基于令牌桶算法。它允许突发流量,突发量由maxBurstSeconds决定,默认是1.0秒,即最多可以累积1秒的令牌数。
-
SmoothWarmingUp在令牌桶的基础上增加了预热阶段。它使用了一个冷启动因子(coldFactor),默认是3.0,这意味着冷启动时的速率是稳定速率的1/3。
java
/**
* 冷启动因子(coldFactor)的深入解析
*/
public class ColdFactorAnalysis {
// coldFactor = 3.0 的设计考量
public void explainColdFactor() {
// coldFactor定义:冷启动间隔 = 稳定间隔 × coldFactor
// 当coldFactor=3.0时:
// - 冷启动间隔 = 3 × 稳定间隔
// - 冷启动速率 = 稳定速率 ÷ 3
double stableRate = 10.0; // 稳定速率:10 QPS
double coldFactor = 3.0;
double stableInterval = 1.0 / stableRate; // 0.1秒
double coldInterval = stableInterval * coldFactor; // 0.3秒
double coldRate = 1.0 / coldInterval; // 3.33 QPS
System.out.println("稳定速率: " + stableRate + " QPS");
System.out.println("冷启动速率: " + coldRate + " QPS");
System.out.println("冷启动因子: " + coldFactor);
// 这意味着:
// - 系统启动时以1/3的稳定速率开始
// - 在预热期内逐渐加速到稳定速率
// - 3.0是一个经验值,平衡了冷启动保护和预热效率
}
// 不同coldFactor的影响对比
public void compareColdFactors() {
double stableRate = 10.0;
double[] coldFactors = {2.0, 3.0, 5.0};
for (double factor : coldFactors) {
double coldRate = stableRate / factor;
System.out.printf("coldFactor=%.1f: 冷启动速率=%.2f QPS, 预热难度=%s%n",
factor, coldRate, factor > 3.0 ? "高" : "低");
}
}
}
3.2 获取令牌方法
3.2.1 方法签名
java
// 阻塞获取一个令牌
public double acquire()
public double acquire(int permits)
// 非阻塞尝试获取令牌
public boolean tryAcquire()
public boolean tryAcquire(int permits)
public boolean tryAcquire(long timeout, TimeUnit unit)
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
3.2.2 应用示范
java
public class TokenAcquisitionDemo {
// 场景1:简单的阻塞式限流
public static class BlockingRateLimit {
private final RateLimiter limiter = RateLimiter.create(5.0); // 5 QPS
public void processItem(Item item) {
// 阻塞直到获取到令牌
double waitTime = limiter.acquire();
log.debug("Waited {} seconds for token", waitTime);
// 处理业务逻辑
processBusinessLogic(item);
}
}
// 场景2:批量操作限流
public static class BatchOperationLimit {
private final RateLimiter limiter = RateLimiter.create(10.0);
public void processBatch(List<Item> items) {
// 批量获取令牌,每个item需要1个令牌
int requiredPermits = items.size();
double waitTime = limiter.acquire(requiredPermits);
log.info("Acquired {} permits after waiting {} seconds",
requiredPermits, waitTime);
// 处理批量操作
processBatchItems(items);
}
}
// 场景3:非阻塞快速失败
public static class NonBlockingLimit {
private final RateLimiter limiter = RateLimiter.create(2.0);
public boolean tryProcess(Message message) {
// 立即返回,不阻塞
if (limiter.tryAcquire()) {
processMessage(message);
return true;
} else {
// 执行降级策略
fallbackHandler(message);
return false;
}
}
}
// 场景4:带超时的限流控制
public static class TimeoutLimit {
private final RateLimiter limiter = RateLimiter.create(5.0);
public Optional<Result> processWithTimeout(Request request) {
// 等待最多100ms获取令牌
if (limiter.tryAcquire(100, TimeUnit.MILLISECONDS)) {
return Optional.of(processRequest(request));
} else {
log.warn("Rate limit timeout for request: {}", request);
return Optional.empty();
}
}
}
}
3.2.3 关键源码
java
/**
* acquire方法:阻塞获取指定数量的令牌
*
* @param permits 请求的令牌数量
* @return 等待的时间(秒)
*/
public double acquire(int permits) {
long microsToWait = reserve(permits);
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return 1.0 * microsToWait / TimeUnit.SECONDS.toMicros(1L);
}
/**
* tryAcquire方法:带超时的尝试获取令牌
*
* @param permits 请求的令牌数量
* @param timeout 超时时间
* @param unit 时间单位
* @return 是否成功获取令牌
*/
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
long timeoutMicros = unit.toMicros(timeout);
long microsToWait = reserve(permits, timeoutMicros);
if (microsToWait == -1) {
return false; // 超时
}
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return true;
}
内部调用链路分析:
java
/**
* acquire方法的完整调用链
* AcquireCallChain类是虚构的,只是为了方便展示里面的方法链路
*/
public class AcquireCallChain {
// 完整的调用层次
public double acquire(int permits) {
// 1. 预留令牌并计算等待时间
long microsToWait = reserve(permits);
// 2. 执行不可中断的睡眠
stopwatch.sleepMicrosUninterruptibly(microsToWait);
// 3. 返回等待时间(转换为秒)
return microsToWait / 1_000_000.0;
}
private long reserve(int permits) {
// 委托给更具体的reserve方法,超时设为最大值(无限等待)
return reserveAndGetWaitLength(permits, stopwatch.readMicros());
}
private long reserveAndGetWaitLength(int permits, long nowMicros) {
synchronized (mutex()) {
// 获取下一个可用时间点
long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
// 计算需要等待的时间
return Math.max(momentAvailable - nowMicros, 0);
}
}
}
⭐️ 计算所需时间的核心方法:
java
/**
* reserveEarliestAvailable方法完整实现逻辑
*/
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
// 步骤1: 时间同步,更新存储的令牌
resync(nowMicros);
// 步骤2: 记录返回值(当前下一个可用时间点)
long returnValue = nextFreeTicketMicros;
// 步骤3: 计算可用的存储令牌数量
double storedPermitsToSpend = Math.min(requiredPermits, this.storedPermits);
// 步骤4: 计算需要等待生成的新鲜令牌数量
double freshPermits = requiredPermits - storedPermitsToSpend;
// 步骤5: 计算总等待时间
long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros);
// 步骤6: 更新下一个可用时间点
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
// 步骤7: 更新存储令牌数量
this.storedPermits -= storedPermitsToSpend;
// 步骤8: 返回之前记录的值
return returnValue;
}
/**
* resync方法:根据当前时间更新存储的令牌数量
*/
void resync(long nowMicros) {
// 如果当前时间晚于下一个可用时间点,说明有新的令牌生成
if (nowMicros > nextFreeTicketMicros) {
// 计算新生成的令牌数量
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
// 更新存储令牌数量(不超过最大值)
storedPermits = Math.min(maxPermits, storedPermits + newPermits);
// 更新下一个可用时间点为当前时间
nextFreeTicketMicros = nowMicros;
}
}
超时处理的逻辑:
java
/**
* tryAcquire的超时处理机制
*/
public class TryAcquireTimeoutHandling {
// 带超时的reserve方法
private long reserve(int permits, long timeoutMicros) {
synchronized (mutex()) {
long nowMicros = stopwatch.readMicros();
// 检查是否可以在超时前获得令牌
if (!canAcquire(nowMicros, timeoutMicros)) {
return -1; // 超时
}
return reserveAndGetWaitLength(permits, nowMicros);
}
}
// 判断是否可以在超时前获取
private boolean canAcquire(long nowMicros, long timeoutMicros) {
// 查询最早可用时间(不修改状态)
long earliestAvailable = queryEarliestAvailable(nowMicros);
// 最早可用时间 - 当前时间 <= 超时时间
return (earliestAvailable - nowMicros) <= timeoutMicros;
}
// 查询最早可用时间(只读操作)
private long queryEarliestAvailable(long nowMicros) {
// 重新同步时间
resync(nowMicros);
// 返回下一个免费票证时间
return nextFreeTicketMicros;
}
// 实际应用场景
public class PracticalUsage {
private final RateLimiter limiter = RateLimiter.create(10.0);
public boolean processWithTimeout(Request request, long timeoutMs) {
// 尝试在指定超时时间内获取令牌
if (limiter.tryAcquire(1, timeoutMs, TimeUnit.MILLISECONDS)) {
// 成功获取,处理请求
return processRequest(request);
} else {
// 超时,执行降级策略
return fallbackHandler(request);
}
}
// 批量处理带超时
public boolean processBatchWithTimeout(List<Item> items, long timeoutMs) {
int requiredPermits = items.size();
if (limiter.tryAcquire(requiredPermits, timeoutMs, TimeUnit.MILLISECONDS)) {
// 成功获取所有需要的令牌
processBatch(items);
return true;
} else {
// 无法在超时内获取全部令牌,尝试分批处理
return processBatchIncrementally(items, timeoutMs);
}
}
}
}
这里给出一个测试case,感兴趣的可以试一试:
java
/**
* 超时计算的边界情况处理
*/
public class TimeoutEdgeCases {
// 各种边界情况的测试
public void testTimeoutEdgeCases() {
RateLimiter limiter = RateLimiter.create(1.0); // 1 QPS
// 情况1:超时时间为0(非阻塞检查)
boolean immediateResult = limiter.tryAcquire(0, TimeUnit.MILLISECONDS);
System.out.println("立即检查结果: " + immediateResult);
// 情况2:超时时间为负(视为0)
boolean negativeResult = limiter.tryAcquire(-1, TimeUnit.MILLISECONDS);
System.out.println("负超时结果: " + negativeResult);
// 情况3:请求0个令牌(总是成功)
boolean zeroPermits = limiter.tryAcquire(0, 100, TimeUnit.MILLISECONDS);
System.out.println("0令牌请求: " + zeroPermits); // 总是true
// 情况4:非常大的超时时间(接近无限等待)
boolean longWait = limiter.tryAcquire(10, Long.MAX_VALUE, TimeUnit.MICROSECONDS);
System.out.println("长等待结果: " + longWait); // 相当于acquire()
}
// 时间单位转换的精确性
public void testTimeUnitPrecision() {
RateLimiter limiter = RateLimiter.create(1000.0); // 1000 QPS
// 不同时间单位的转换测试
long timeoutNs = 1000; // 1微秒
long timeoutMicros = TimeUnit.NANOSECONDS.toMicros(timeoutNs); // 1微秒
long timeoutMs = TimeUnit.NANOSECONDS.toMillis(timeoutNs); // 0毫秒(向下取整)
// 这会导致精度丢失!
boolean result1 = limiter.tryAcquire(1, timeoutNs, TimeUnit.NANOSECONDS);
boolean result2 = limiter.tryAcquire(1, timeoutMicros, TimeUnit.MICROSECONDS);
boolean result3 = limiter.tryAcquire(1, timeoutMs, TimeUnit.MILLISECONDS);
System.out.println("纳秒超时: " + result1); // 可能true
System.out.println("微秒超时: " + result2); // 可能true
System.out.println("毫秒超时: " + result3); // false(0毫秒)
}
}
3.3 动态调整速率
3.3.1 方法签名
java
public final void setRate(double permitsPerSecond)
public final double getRate()
3.3.2 应用示范
java
public class DynamicRateAdjustment {
// 场景1:根据系统负载动态调整限流
public static class AdaptiveRateLimiter {
private final RateLimiter limiter = RateLimiter.create(50.0);
private final SystemMonitor monitor = new SystemMonitor();
@Scheduled(fixedRate = 10000) // 每10秒调整一次
public void adjustRateBasedOnLoad() {
double systemLoad = monitor.getSystemLoad();
double newRate = calculateOptimalRate(systemLoad);
if (Math.abs(newRate - limiter.getRate()) > 5.0) {
limiter.setRate(newRate);
log.info("Adjusted rate limit from {} to {}", limiter.getRate(), newRate);
}
}
private double calculateOptimalRate(double load) {
if (load > 0.8) return 30.0; // 高负载,降低速率
if (load > 0.5) return 40.0; // 中负载,适中速率
return 50.0; // 低负载,最高速率
}
}
// 场景2:不同时间段的限流策略
public static class TimeBasedRateLimiter {
private final RateLimiter limiter = RateLimiter.create(100.0);
@Scheduled(cron = "0 0 9 * * ?") // 每天9点
public void setDaytimeRate() {
limiter.setRate(200.0); // 白天提高限流
}
@Scheduled(cron = "0 0 18 * * ?") // 每天18点
public void setEveningRate() {
limiter.setRate(100.0); // 晚上恢复正常
}
@Scheduled(cron = "0 0 23 * * ?") // 每天23点
public void setNightRate() {
limiter.setRate(50.0); // 深夜降低限流
}
}
// 场景3:基于业务优先级的动态限流
public static class PriorityBasedLimiter {
private final RateLimiter highPriorityLimiter = RateLimiter.create(100.0);
private final RateLimiter normalPriorityLimiter = RateLimiter.create(50.0);
public void processHighPriorityTask(Task task) {
highPriorityLimiter.acquire();
executeHighPriorityTask(task);
}
public void processNormalPriorityTask(Task task) {
normalPriorityLimiter.acquire();
executeNormalPriorityTask(task);
}
// 根据业务情况动态调整优先级配额
public void adjustPriorityQuota(double highPriorityRatio) {
double totalRate = 150.0; // 总QPS
double highRate = totalRate * highPriorityRatio;
double normalRate = totalRate - highRate;
highPriorityLimiter.setRate(highRate);
normalPriorityLimiter.setRate(normalRate);
}
}
}
3.3.3 关键源码
java
/**
* setRate方法:动态设置RateLimiter的速率
*
* @param permitsPerSecond 新的速率(每秒允许的许可数)
* @throws IllegalArgumentException 如果参数不合法
*/
public final void setRate(double permitsPerSecond) {
checkArgument(permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond),
"rate must be positive");
synchronized (mutex()) {
doSetRate(permitsPerSecond, stopwatch.readMicros());
}
}
使用专用mutex保证线程安全:
java
private Object mutex() {
Object mutex = mutexDoNotUseDirectly;
if (mutex == null) {
synchronized (this) {
mutex = mutexDoNotUseDirectly;
if (mutex == null) {
mutexDoNotUseDirectly = mutex = new Object();
}
}
}
return mutex;
}
以SmoothBursty.doSetRate的实现为例:
java
// SmoothRateLimiter中的实现
final void doSetRate(double permitsPerSecond, long nowMicros) {
resync(nowMicros); // 步骤1:根据当前时间同步令牌状态
double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond; // 步骤2:计算新的稳定间隔
this.stableIntervalMicros = stableIntervalMicros; // 步骤3:更新稳定间隔
doSetRate(permitsPerSecond, stableIntervalMicros); // 步骤4:调用子类具体实现
}
// SmoothBursty中的具体实现
void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
// 保存旧的最大许可数,用于后续的比例计算
double oldMaxPermits = this.maxPermits;
// 计算新的最大许可数 = 最大突发秒数 × 新速率
// 例如:maxBurstSeconds=1.0, permitsPerSecond=10 → maxPermits=10
maxPermits = maxBurstSeconds * permitsPerSecond;
// 处理特殊情况:如果旧的最大许可数是无穷大
if (oldMaxPermits == Double.POSITIVE_INFINITY) {
// 从无限容量切换到有限容量,直接填满新的令牌桶
storedPermits = maxPermits;
} else {
// 正常情况:按比例调整存储的许可数
storedPermits = (oldMaxPermits == 0.0)
? 0.0 // 初始状态,存储许可数为0
: storedPermits * maxPermits / oldMaxPermits; // 保持充满比例不变
}
}}
getRate方法的实现比较简单:
java
public final double getRate() {
synchronized (mutex()) {
return doGetRate();
}
}
@Override
final double doGetRate() {
return SECONDS.toMicros(1L) / stableIntervalMicros;
}
4. 总结
通过本文的深入探讨,我们可以看到Google Guava RateLimiter作为一个成熟可靠的限流组件,在现代分布式系统中扮演着至关重要的角色。通过令牌桶算法实现,它既能限制平均速率 ,又能允许合理的突发流量,这种设计完美契合了互联网应用流量波动大的特点。
两种工作模式各具特色:
- 平滑突发模式(SmoothBursty):适合大多数通用场景,在保证平均速率的同时允许短暂突发
- 平滑预热模式(SmoothWarmingUp):针对冷启动系统设计,避免瞬时压力冲击
不过,RateLimiter主要适用于单机环境,在微服务架构和分布式系统中确实存在局限性。现代系统通常需要更全面的解决方案,下面给出一些参考:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 单体应用内部限流 | Guava RateLimiter | 轻量、简单、性能好 |
| 微服务API网关 | Sentinel | 功能全面、生态完善 |
| 老系统改造 | Hystrix | 成熟稳定、文档丰富 |
| 云原生应用 | Resilience4j | 轻量、云原生友好 |