Guava 限流器源码解析

限流器介绍了限流器的概念以及四种主流的限流算法,不过其中的代码样例比较简单,无法在生产环境直接使用,这篇文章介绍下Google开源的Guava中实现的限流器Guava 限流器

public static RateLimiter create(double permitsPerSecond) {
    return create(permitsPerSecond, RateLimiter.SleepingStopwatch.createFromSystemTimer());
}

// 实际上初始化的是SmoothBursty实例
static RateLimiter create(double permitsPerSecond, RateLimiter.SleepingStopwatch stopwatch) {
    RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0D);
    rateLimiter.setRate(permitsPerSecond);
    return rateLimiter;
}

Guava提供了acquire方法来执行具体的限流操作

public double acquire() {
    return this.acquire(1);
}

@CanIgnoreReturnValue
public double acquire(int permits) {
    long microsToWait = this.reserve(permits);
    this.stopwatch.sleepMicrosUninterruptibly(microsToWait); // 休眠指定的时间,不响应中断
    return 1.0D * (double)microsToWait / (double) TimeUnit.SECONDS.toMicros(1L);
}

final long reserve(int permits) {
    checkPermits(permits); // 检查传入的参数是否正确
	// 临界区,这里如果permits数量能够被满足说明不会触发限流,否则返回的是至少要等待的时间,多少时间后可以被处理
    synchronized(this.mutex()) {
        return this.reserveAndGetWaitLength(permits, this.stopwatch.readMicros());
    }
}

Guava限流器的令牌补充以及计算等待时间等逻辑都是在reserveEarliestAvailable实现的

final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
    this.resync(nowMicros); //惰性补充令牌
    long returnValue = this.nextFreeTicketMicros; // 本次请求的最早可用时间
    double storedPermitsToSpend = Math.min((double)requiredPermits, this.storedPermits); // 存量令牌的消耗数
    double freshPermits = (double)requiredPermits - storedPermitsToSpend; // 还需要生成多少令牌N才能满足本次请求
    long waitMicros = this.storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) + (long)(freshPermits * this.stableIntervalMicros); // 存量令牌消耗完后,N个令牌尚未存在,需等待时间才能生成
    this.nextFreeTicketMicros = LongMath.saturatedAdd(this.nextFreeTicketMicros, waitMicros); // 现场生成N个令牌会影响之后请求的最早响应时间,所以更新下次请求的最早可用时间
    this.storedPermits -= storedPermitsToSpend;
    return returnValue;
}

resync补充令牌,Guava是在请求到来时根据上次补充令牌的时间差惰性补充令牌,对CPU比较友好

void resync(long nowMicros) {
    if (nowMicros > this.nextFreeTicketMicros) {
        double newPermits = (double)(nowMicros - this.nextFreeTicketMicros) / this.coolDownIntervalMicros();
        this.storedPermits = Math.min(this.maxPermits, this.storedPermits + newPermits);
        this.nextFreeTicketMicros = nowMicros;
    }
}

acquire方法是阻塞等待,也就是如果请求没有获取到令牌,会睡眠直到令牌桶中的令牌足够其响应,还有一个非阻塞等待方法tryAcquire

public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
    long timeoutMicros = Math.max(unit.toMicros(timeout), 0L);
    checkPermits(permits);
    long microsToWait;
    synchronized(this.mutex()) {
        long nowMicros = this.stopwatch.readMicros();
        if (!this.canAcquire(nowMicros, timeoutMicros)) { // 比较最早可用时间与超时时间
            return false;
        }

        microsToWait = this.reserveAndGetWaitLength(permits, nowMicros);
    }

    this.stopwatch.sleepMicrosUninterruptibly(microsToWait);
    return true;
}

tryAcquire通过比较令牌的最早可用时间和超时时间,来判断是否可以获取到令牌,如果超时时间内可以获取,则阻塞等待,否则的话直接返回false。

Guava限流器依赖时钟进行令牌以及最早可用时间等参数的计算,因此时钟的精度决定了限流器的限流准确性。由于系统时钟存在回拨、精度低等问题,Guava采用逻辑时钟,即以限流器创建时间为起点,是一个单调递增的时钟,时钟精度达到纳秒级,非常适合限流场景。限流算法必须依赖一个“稳定、单调、高精度”的时间源

posted @ 2025-12-15 13:26  xxs不是小学生  阅读(6)  评论(0)    收藏  举报