RateLimiter限流:单机高并发系统的守护神

1. 前言

核心源码可参考:Google Guava RateLimiter

在当今的互联网应用中,高并发场景无处不在。无论是电商平台的秒杀活动、社交媒体的热点事件,还是API服务的突发流量,都可能对系统造成巨大压力。如果没有适当的保护措施,系统很容易被流量冲垮,导致服务不可用。

限流(Rate Limiting) 就是在这种背景下应运而生的一种关键技术。它就像交通信号灯一样,控制着请求的流量,确保系统在承受范围内稳定运行。而Google Guava库中的RateLimiter则是Java领域最常用的限流工具之一。

想象一下这样的场景:你的API服务突然因为某个网红推荐而流量暴增,如果没有限流,数据库连接可能被耗尽,CPU飙升至100%,最终导致整个服务崩溃。而有了RateLimiter,你可以平稳地处理这些流量,虽然可能会拒绝部分请求,但保证了核心服务的可用性。

本文主要介绍了:

  1. 突发限流和预热限流
  2. 限流的令牌方法实现
  3. 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的子类。

--- title: Guava RateLimiter Class Diagram --- classDiagram direction BT class RateLimiter { <> # SleepingStopwatch stopwatch + create(double permitsPerSecond) RateLimiter + create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) RateLimiter + acquire() double + acquire(int permits) double + tryAcquire() boolean + tryAcquire(long timeout, TimeUnit unit) boolean + tryAcquire(int permits) boolean + tryAcquire(int permits, long timeout, TimeUnit unit) boolean # doGetRate()* double # doSetRate(double permitsPerSecond, long nowMicros)* void # queryEarliestAvailable(long nowMicros)* long # reserveEarliestAvailable(int permits, long nowMicros)* long } class SmoothRateLimiter { <> # double storedPermits # double maxPermits # double stableIntervalMicros # long nextFreeTicketMicros # coolDownIntervalMicros()* long } class SmoothBursty { # double maxBurstSeconds # coolDownIntervalMicros() long } class SmoothWarmingUp { # long warmupPeriodMicros # double coldFactor # double slope # double thresholdPermits # coolDownIntervalMicros() long } RateLimiter <|-- SmoothRateLimiter SmoothRateLimiter <|-- SmoothBursty SmoothRateLimiter <|-- SmoothWarmingUp
  • 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 轻量、云原生友好
相关推荐
派大鑫wink17 小时前
【JAVA学习日志】SpringBoot 参数配置:从基础到实战,解锁灵活配置新姿势
java·spring boot·后端
程序员爱钓鱼17 小时前
Node.js 编程实战:文件读写操作
前端·后端·node.js
xUxIAOrUIII17 小时前
【Spring Boot】控制器Controller方法
java·spring boot·后端
Dolphin_Home17 小时前
从理论到实战:图结构在仓库关联业务中的落地(小白→中级,附完整代码)
java·spring boot·后端·spring cloud·database·广度优先·图搜索算法
zfj32117 小时前
go为什么设计成源码依赖,而不是二进制依赖
开发语言·后端·golang
weixin_4624462318 小时前
使用 Go 实现 SSE 流式推送 + 打字机效果(模拟 Coze Chat)
开发语言·后端·golang
JIngJaneIL18 小时前
基于springboot + vue古城景区管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
小信啊啊18 小时前
Go语言切片slice
开发语言·后端·golang
Loo国昌19 小时前
Vue 3 前端工程化:架构、核心原理与生产实践
前端·vue.js·架构
tap.AI19 小时前
RAG系列(一) 架构基础与原理
人工智能·架构