前言
刚学了Sentinel中的限流算法,它是基于Guava的限流算法,因此,有必要学习一下Guava限流的实现。一、示例
@Test
public void rateLimiterTest() {
//每秒生成5个许可
RateLimiter rateLimiter = RateLimiter.create(5);
//获取许可,可能会等待
rateLimiter.acquire(1);
//获取许可,可能会等待超时
rateLimiter.tryAcquire(1,200, TimeUnit.MILLISECONDS);
}
二、创建流程
1、创建入口create( )
public static RateLimiter create(double permitsPerSecond) {
return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
}
2、计时器
(1)createFromSystemTimer( )
public static SleepingStopwatch createFromSystemTimer() {
return new SleepingStopwatch() {
//创建计时器并启动
final Stopwatch stopwatch = Stopwatch.createStarted();
@Override
//纳秒转微秒
protected long readMicros() {
return stopwatch.elapsed(MICROSECONDS);
}
@Override
//休眠等待
protected void sleepMicrosUninterruptibly(long micros) {
if (micros > 0) {
Uninterruptibles.sleepUninterruptibly(micros, MICROSECONDS);
}
}
};
}
(2)启动计时器
public static Stopwatch createStarted() {
return new Stopwatch().start();
}
Stopwatch() {
this.ticker = Ticker.systemTicker();
}
创建Ticker,用于获取系统时间,单位是纳秒
public abstract class Ticker {
/** Constructor for use by subclasses. */
protected Ticker() {}
/** Returns the number of nanoseconds elapsed since this ticker's fixed point of reference. */
public abstract long read();
/**
* A ticker that reads the current time using {@link System#nanoTime}.
*
* @since 10.0
*/
public static Ticker systemTicker() {
return SYSTEM_TICKER;
}
private static final Ticker SYSTEM_TICKER =
new Ticker() {
@Override
public long read() {
//获取系统的纳秒时间
return Platform.systemNanoTime();
}
};
}
计时器启动
public Stopwatch start() {
checkState(!isRunning, "This stopwatch is already running.");
//标识状态
isRunning = true;
//计时器启动时间
startTick = ticker.read();
return this;
}
(3)计时器单位转换
public long elapsed(TimeUnit desiredUnit) {
return desiredUnit.convert(elapsedNanos(), NANOSECONDS);
}
private long elapsedNanos() {
//计算计时器流逝时间
return isRunning ? ticker.read() - startTick + elapsedNanos : elapsedNanos;
}
单位转换
public long convert(long sourceDuration, TimeUnit sourceUnit) {
switch (this) {
case NANOSECONDS: return sourceUnit.toNanos(sourceDuration);
case MICROSECONDS: return sourceUnit.toMicros(sourceDuration);
case MILLISECONDS: return sourceUnit.toMillis(sourceDuration);
case SECONDS: return sourceUnit.toSeconds(sourceDuration);
default: return cvt(sourceDuration, scale, sourceUnit.scale);
}
}
(4)计时器停止
public Stopwatch stop() {
//当前时间
long tick = ticker.read();
checkState(isRunning, "This stopwatch is already stopped.");
//标识状态
isRunning = false;
//流逝的时间,秒表还可以再次启动停止,流逝时间会递增
elapsedNanos += tick - startTick;
return this;
}
3、创建限流器
接着第1步说限流器的流程
(1)create( )
static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
//创建 SmoothBursty ,允许突发流量的平稳限流器
RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
//设置速率
rateLimiter.setRate(permitsPerSecond);
return rateLimiter;
}
(2)SmoothBursty( )
SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
super(stopwatch);
//突发流量时间,默认是1s,貌似没有什么用处
this.maxBurstSeconds = maxBurstSeconds;
}
(3)rateLimiter.setRate( )
public final void setRate(double permitsPerSecond) {
checkArgument(
permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");
//加互斥锁
synchronized (mutex()) {
//设置速率,
doSetRate(permitsPerSecond, stopwatch.readMicros());
}
}
(4)doSetRate( )
permitsPerSecond 表示每秒生成许可数,nowMicros 表示秒表运行时间
final void doSetRate(double permitsPerSecond, long nowMicros) {
//计算许可,更新下次免费获取许可时间
resync(nowMicros);
//稳定生产一个许可需要的时间
double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
this.stableIntervalMicros = stableIntervalMicros;
//设置速率
doSetRate(permitsPerSecond, stableIntervalMicros);
}
(5)resync(nowMicros)
void resync(long nowMicros) {
// if nextFreeTicket is in the past, resync to now
//计时器运行时间大于下次免费获取许可的时间
if (nowMicros > nextFreeTicketMicros) {
//计算新增的许可数量
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
//计算当前总的许可数量
storedPermits = min(maxPermits, storedPermits + newPermits);
//更新下次免费获取许可时间
nextFreeTicketMicros = nowMicros;
}
}
生成一个许可的时间
SmoothBursty.java
double coolDownIntervalMicros() {
return stableIntervalMicros;
}
(6) doSetRate(permitsPerSecond, stableIntervalMicros)
初始化storedPermits 的值
SmoothBursty.java
void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
double oldMaxPermits = this.maxPermits;
maxPermits = maxBurstSeconds * permitsPerSecond;
if (oldMaxPermits == Double.POSITIVE_INFINITY) {
// if we don't special-case this, we would get storedPermits == NaN, below
storedPermits = maxPermits;
} else {
storedPermits =
(oldMaxPermits == 0.0)
? 0.0 // initial state
: storedPermits * maxPermits / oldMaxPermits;
}
}
三、获取流程
1、acquire(int permits)
(1)acquire( )
public double acquire(int permits) {
//计算需要等待的时间
long microsToWait = reserve(permits);
//休眠等待
stopwatch.sleepMicrosUninterruptibly(microsToWait);
//返回等待的时间
return 1.0 * microsToWait / SECONDS.toMicros(1L);
}
(2)reserve( )
final long reserve(int permits) {
checkPermits(permits);
synchronized (mutex()) {
return reserveAndGetWaitLength(permits, stopwatch.readMicros());
}
}
final long reserveAndGetWaitLength(int permits, long nowMicros) {
//计算获取到许可的最早时间
long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
//最早时间减去秒表运行时间,计算出等待时间
return max(momentAvailable - nowMicros, 0);
}
(3)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 =
storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros);
//更新下次免费获取许可时间,加上本次等待的时间
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
//剩余的许可
this.storedPermits -= storedPermitsToSpend;
//返回下次免费获取许可时间,并没有加上本次需要等待的时间,因此本次等待时间会影响下次请求
return returnValue;
}
2、tryAcquire(1,200, TimeUnit.MILLISECONDS)
(1)tryAcquire( )
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
//超时时间转成微秒
long timeoutMicros = max(unit.toMicros(timeout), 0);
checkPermits(permits);
long microsToWait;
synchronized (mutex()) {
//计时器运行的时间
long nowMicros = stopwatch.readMicros();
//校验是否能够获取令牌
if (!canAcquire(nowMicros, timeoutMicros)) {
return false;
} else {
//获取等待时间
microsToWait = reserveAndGetWaitLength(permits, nowMicros);
}
}
//休眠等待
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return true;
}
可以看出,tryAcquire( ) 比 acquire( ) 多了canAcquire( ) 方法
下次能获取免费许可的最早时间 减去 超时时间,比当前运行时间小就能够获取令牌
private boolean canAcquire(long nowMicros, long timeoutMicros) {
return queryEarliestAvailable(nowMicros) - timeoutMicros <= nowMicros;
}
final long queryEarliestAvailable(long nowMicros) {
return nextFreeTicketMicros;
}
总结
SmoothBursty 限流器使用的是令牌桶算法,默认能够存储一定数量的令牌,也能够应对突发的流量,突发的流量需要等待时间,但是等待时间不影响本次请求,影响的是后续请求。