RatleLimiter源码分析
本文基于版本
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.0-jre</version>
</dependency>
先看下
RateLimiter
中的类图
RateLimiter
中的成员变量
/**
* 主要用作计时器 SleepingStopwatch是rateLimiter中的一个内部类,创建时就启动了
* {@link Stopwatch.createStarted()}
*/
private final SleepingStopwatch stopwatch;
private volatile @Nullable Object mutexDoNotUseDirectly;
但是
Object mutexDoNotUseDirectly
我就没明白为什么要这么设计? 请读者不吝赐教。
private Object mutex() {
Object mutex = mutexDoNotUseDirectly;
if (mutex == null) {
synchronized (this) {
mutex = mutexDoNotUseDirectly;
if (mutex == null) {
mutexDoNotUseDirectly = mutex = new Object();
}
}
}
return mutex;
}
//用到的地方都是 这里防止并发到底有没有必要这么去设计? 这么设计有什么好处? 考虑点在哪儿?
synchronized (mutex()) {
}
SmoothRateLimiter
的成员变量
/** 当前存储的permits数量 */
double storedPermits;
/** 最大允许缓存的 permits 数量,也就是 storedPermits 能达到的最大值 */
double maxPermits;
/**
* 每隔多少时间产生一个 permit,例如 1s- > 5permit 则200ms/1permit
*/
double stableIntervalMicros;
/**
* 下一次可以获取 permits 的时间,这个时间是根据stopwatch.readMicros()来的,每次请求时都会刷新这个时间
*/
private long nextFreeTicketMicros = 0L;
SmoothBursty
分析
RateLimiter
的静态构造方法:
public static RateLimiter create(double permitsPerSecond) {
//在创建{@link RateLimiter}时就启动{@link SleepingStopwatch}
return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
}
/*
* maxBurstSeconds=1.0,最多会缓存1s的请求数到池中,有0 <= storedPermits <= maxPermits = 1*permitsPerSecond
*/
static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
rateLimiter.setRate(permitsPerSecond);
return rateLimiter;
}
我们继续往后看
setRate()
方法,这个方法时调控permits的速率的:
public final void setRate(double permitsPerSecond) {
checkArgument(
permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");
// 这个控制并发为何如此设计, 有哪位读者可否解答下!!!!!
synchronized (mutex()) {
doSetRate(permitsPerSecond, stopwatch.readMicros());
}
}
final void doSetRate(double permitsPerSecond, long nowMicros) {
// 这个方法需要看下
resync(nowMicros);
double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
this.stableIntervalMicros = stableIntervalMicros;
doSetRate(permitsPerSecond, stableIntervalMicros);
}
void resync(long nowMicros) {
/**
* 如果很长时间没有请求limiter.acquire(),这段时间会内生成了很多permits,需要调整storedPermits
* coolDownIntervalMicros()在SmoothBursty中就是stableIntervalMicros(产生一个permit需要的时间)
*/
if (nowMicros > nextFreeTicketMicros) {
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
storedPermits = min(maxPermits, storedPermits + newPermits);
nextFreeTicketMicros = nowMicros;
}
}
继续
doSetRate()
@Override
void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
double oldMaxPermits = this.maxPermits;
maxPermits = maxBurstSeconds * permitsPerSecond;
if (oldMaxPermits == Double.POSITIVE_INFINITY) {
// Double.POSITIVE_INFINITY(1/0)无穷大
storedPermits = maxPermits;
} else {
// 这个地方可能需要缩放,例如创建RateLimiter后再手动调用RateLimiter.setRate(int x)
storedPermits =(oldMaxPermits == 0.0) ? 0.0 : storedPermits * maxPermits/oldMaxPermits;
}
}
RateLimiter
创建时的源码就到这儿,接下来,我们来分析acquire()
方法:
public boolean tryAcquire() {
return tryAcquire(1, 0, MICROSECONDS);
}
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
long timeoutMicros = max(unit.toMicros(timeout), 0);
// 校验permits>0
checkPermits(permits);
long microsToWait;
synchronized (mutex()) {
long nowMicros = stopwatch.readMicros();
if (!canAcquire(nowMicros, timeoutMicros)) {
return false;
} else {
microsToWait = reserveAndGetWaitLength(permits, nowMicros);
}
}
// SmoothBursty 下 microsToWait=0
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return true;
}
/**
* queryEarliestAvailable(nowMicros)返回 nextFreeTicketMicros
* 如果有预占的情况时nextFreeTicketMicros = nowMicros+(生成预占数量的时间)
*/
private boolean canAcquire(long nowMicros, long timeoutMicros) {
return queryEarliestAvailable(nowMicros) - timeoutMicros <= nowMicros;
}
我们看下
reserveAndGetWaitLength()
方法:
/**
* Reserves next ticket and returns the wait time that the caller must wait for.
* @return the required wait time, never negative
*/
final long reserveAndGetWaitLength(int permits, long nowMicros) {
long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
return max(momentAvailable - nowMicros, 0);
}
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
// 跟上面分析一样重新调整nextFreeTicketMicros 和 storedPermits,每次请求时都需调整
resync(nowMicros);
long returnValue = nextFreeTicketMicros;
double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
// 缓存的permits不足数量,用来计算预占时间
double freshPermits = requiredPermits - storedPermitsToSpend;
// SmoothBursty中storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)=0,预热的时间
long waitMicros =
storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
// SmoothWarmingUp模式中超过的permits也相当于匀速生成
+ (long) (freshPermits * stableIntervalMicros);
// 重新计算需加上预支的时间
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
this.storedPermits -= storedPermitsToSpend;
// 返回上次计算的值
return returnValue;
}
SmoothBursty
分析完了,代码不多,逻辑也挺简单,需要注意的是在RateLimiter.create(5)
创建时storedPermits=0
,maxPermits=5.0
,只有在第一次rateLimiter.tryAcquire()
时才会根据时间差计算出storedPermits
的值(见resync(long nowMicros)
) 。
SmoothWarmingUp
分析
SmoothWarmingUp
适用于资源需要预热的场景。比如我们的某个接口业务,需要使用到数据库连接,由于连接需要预热才能进入到最佳状态,如果我们的系统长时间处于低负载或零负载状态(当然,应用刚启动也是一样的),连接池中的连接慢慢释放掉了,此时我们认为连接池是冷的。先看一张图:
再结合
doSetRate()
源码看下计算公式
void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
double oldMaxPermits = maxPermits;
// coldFactor = 3 硬编码
double coldIntervalMicros = stableIntervalMicros * coldFactor;
/**
* 至于为什么是 1/2 我是没搞明白, 有谁知道的,请赐教? 源码中有如下解释:
* <li>The time to go from thresholdPermits to 0 is equal to the integral of the function
* between 0 and thresholdPermits. This is thresholdPermits * stableIntervals. By (5) it is
* also equal to warmupPeriod/2. Therefore
* <blockquote>
* thresholdPermits = 0.5 * warmupPeriod / stableInterval
* </blockquote>
* <li>
* 至于为什么从thresholdPermits到0的时间等于从maxPermits到thresholdPermits的时间,我没明白!!
*/
thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros;
maxPermits =
thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros);
//计算梯形的斜率
slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits - thresholdPermits);
if (oldMaxPermits == Double.POSITIVE_INFINITY) {
// if we don't special-case this, we would get storedPermits == NaN, below
storedPermits = 0.0;
} else {
// 初始化时SmoothBursty中storedPermits=0,而SmoothWarmingUp中storedPermits= maxPermits
storedPermits = (oldMaxPermits == 0.0)? maxPermits : storedPermits * maxPermits / oldMaxPermits;
}
}
RateLimiter
的静态构造方法:
public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) {
checkArgument(warmupPeriod >= 0, "warmupPeriod must not be negative: %s", warmupPeriod);
return create(permitsPerSecond, warmupPeriod, unit, 3.0, SleepingStopwatch.createFromSystemTimer());
}
static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit,
double coldFactor, SleepingStopwatch stopwatch) {
// 可以看到coldFactor的硬编码为3
RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit, coldFactor);
rateLimiter.setRate(permitsPerSecond);
return rateLimiter;
}
和
SmoothBursty
中基本一致,区别就在
构造函数以及上面的doSetRate()
源码,下面分析下tryAcquire()
这个核心方法,先看下和SmoothBursty
中resync()
的区别
void resync(long nowMicros) {
// if nextFreeTicket is in the past, resync to now
if (nowMicros > nextFreeTicketMicros) {
// 和SmoothBursty 的区别在 coolDownIntervalMicros()
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
storedPermits = min(maxPermits, storedPermits + newPermits);
nextFreeTicketMicros = nowMicros;
}
}
// 预热时间/最大permits
double coolDownIntervalMicros() {
return warmupPeriodMicros / maxPermits;
}
看下和
SmoothBursty
中reserveEarliestAvailable()
的区别
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
resync(nowMicros);
long returnValue = nextFreeTicketMicros;
double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
double freshPermits = requiredPermits - storedPermitsToSpend;
long waitMicros =
// 和SmoothBursty的区别在 storedPermitsToWaitTime(),SmoothBursty 中为0
storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros);
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
this.storedPermits -= storedPermitsToSpend;
return returnValue;
}
/**
* permitsToTake 可允许获取的permits数量
* min(requiredPermits, this.storedPermits)
*/
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
// 超过thresholdPermits部分
double availablePermitsAboveThreshold = storedPermits - thresholdPermits;
long micros = 0;
if (availablePermitsAboveThreshold > 0.0) {
// permitsToTake 可获取的permits min(storedPermits,requiredPermits)
double permitsAboveThresholdToTake = min(availablePermitsAboveThreshold, permitsToTake);
double length =
permitsToTime(availablePermitsAboveThreshold)
+ permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake);
micros = (long) (permitsAboveThresholdToTake * length / 2.0);
permitsToTake -= permitsAboveThresholdToTake;
}
micros += (long) (stableIntervalMicros * permitsToTake);
return micros;
}
// 超过梯形的x值计算y值
private double permitsToTime(double permits) {
return stableIntervalMicros + permits * slope;
}
贴个图,可以仔细体会下
源码到这儿就分析结束了,
SmoothWarmingUp
要复杂不少,代码不重要,主要是要读懂guava这样设计的思想。
有时间的话,分析比较下sentinel
和gateway
中的限流。