RateLimiter的核心思路
如下图所示,我创建一个1秒产生0.1的RateLimiter(即10秒产生1个),左边是时间轴,现在有3个线程申请数据,nextFreeTicketMicros初始化为0(其实他的计算单位是微秒)
当1点1分0秒时,此时nextFreeTicketMicros = 0秒,线程A开始申请数据,当线程A申请到数据后会把nextFreeTicketMicros设置成10秒(别问为什么设置成10秒),此时线程A执行完毕
当1点1分3秒时,此时nextFreeTicketMicros = 10秒,线程B开始申请数据,因为此时nextFreeTicketMicros = 10秒,然后把nextFreeTicketMicros 设置为20秒(别问为什么设置成20秒) ,RateLimiter里面的时间计数器为3秒,所以要睡7秒(就是线程B红色的地方在睡觉),此时线程B执行完毕
当1点1分7秒时,此时nextFreeTicketMicros = 20秒,线程C开始申请数据,因为此时nextFreeTicketMicros = 20秒,然后nextFreeTicketMicros设置成30秒(别问为什么设置成30秒),RateLimiter里面的时间计数器为7秒,所以要睡14秒(就是线程C红色的地方在睡觉),此时线程C执行完毕
注意:
分析的上面这种情况下,就是每秒产生0.1个。
先设置nextFreeTicketMicros,在sleep(如果需要的话)
Demo
代码语言:javascript复制//创建一个一秒产生0.1个令牌的实例
RateLimiter rateLimiter = RateLimiter.create(0.1);
//0秒拿到令牌
System.out.println(rateLimiter.acquire(1));
//10秒拿到令牌
System.out.println(rateLimiter.acquire(1));
//20秒拿到令牌
System.out.println(rateLimiter.acquire(1));
RateLimiter初始化
1调用RateLimiter.create方法,传入参数0.1,表示每秒允许0.1个令牌,即10秒创建1个令牌
代码语言:javascript复制//每秒创建0.1个令牌,即10秒创建一个令牌
RateLimiter rateLimiter = RateLimiter.create(0.1);
其中permitsPersecond = 0.1
代码语言:javascript复制public static RateLimiter create(double permitsPerSecond) {
return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
其中permitsPersecond = 0.1
代码语言:javascript复制static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
rateLimiter.setRate(permitsPerSecond);
return rateLimiter;
}
1.1 看 RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);方法
其中maxBurstSeconds = 1.0秒,表示放入令牌的频率是1.0秒
代码语言:javascript复制SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
super(stopwatch);
this.maxBurstSeconds = maxBurstSeconds;
}
1.2看rateLimiter.setRate(permitsPerSecond) 方法
此处用了synchronized关键字
代码语言:javascript复制public final void setRate(double permitsPerSecond) {
checkArgument(
permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");
synchronized (mutex()) {
doSetRate(permitsPerSecond, stopwatch.readMicros());
}
}
下面跟doSetRate方法
其中permitsPerSecond = 0.1 stopwatch.readMicros即nowMicros为当前的时间戳
stableIntervalMicros = 1秒 / 0.1 ,即生成每一个令牌的平均时间,单位为纳秒(毫秒的1000分之1)
代码语言:javascript复制@Override
final void doSetRate(double permitsPerSecond, long nowMicros) {
resync(nowMicros);
double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
this.stableIntervalMicros = stableIntervalMicros;
doSetRate(permitsPerSecond, stableIntervalMicros);
}
1.2.1 跟resync方法
啥也没干,就是把当前时间戳赋值给nextFreeTicketMicros
代码语言:javascript复制 void resync(long nowMicros) {
//执行到此处,nowMicros为当前时间戳,nextFreeTicketMicros还没有赋值,即初始化为0
if (nowMicros > nextFreeTicketMicros) {
//进入该分支,但是coolDownIntervalMicros方法返回的是0,所以newPermits=Infinity
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
storedPermits = min(maxPermits, storedPermits newPermits);
//把当前时间戳赋值给nextFreeTicketMicros
nextFreeTicketMicros = nowMicros;
}
}
1.2.2 跟doSetRate(permitsPerSecond, stableIntervalMicros)方法
参数:permitsPerSecond = 0.1 stableIntervalMicros为上面算的 1秒/0.1 = 1.0E7 纳秒
该段逻辑就是给 storedPermits赋值为0
代码语言:javascript复制@Override
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;
}
}
构造方法总结:赋值
maxBurstSeconds = 1.0 令牌桶算法不是有一个定期往桶里放令牌吗,这个参数就时间周期
stableIntervalMicros = 1秒 / 0.1 = 1.0E7纳秒 生成每一个令牌需要多久
nextFreeTicketMicros = 当前时间戳A
storedPermits = 0 当前存储的令牌为 0个
获取锁方法
流程
rateLimiter.acquire(1)
1、其中 permits = 1表示我要获取一个 令牌
代码语言:javascript复制public double acquire(int permits) {
long microsToWait = reserve(permits);
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return 1.0 * microsToWait / SECONDS.toMicros(1L);
}
2、往下跟,其中permits = 1,表示我要获取的一个令牌
代码语言:javascript复制final long reserve(int permits) {
checkPermits(permits);
synchronized (mutex()) {
return reserveAndGetWaitLength(permits, stopwatch.readMicros());
}
}
3、往下跟,其中permits = 1表示我要获取一个令牌,nowMicros表示此时此刻的时间
代码语言:javascript复制final long reserveAndGetWaitLength(int permits, long nowMicros) {
long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
return max(momentAvailable - nowMicros, 0);
}
4、往下跟,其中permits = 1表示我要获取一个令牌,nowMicros表示此时此刻的时间
代码语言:javascript复制final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
//更新nextFreeTicketMicros,很重要
resync(nowMicros);
//用于计算要sleep的时间
long returnValue = nextFreeTicketMicros;
//此时此刻能给你几个令牌 min(你要的和他有的最小值)
double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
//还需要多少个令牌
double freshPermits = requiredPermits - storedPermitsToSpend;
//还需要多少秒
long waitMicros =
storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
(long) (freshPermits * stableIntervalMicros);
//更新nextFreeTicketMicros
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
//拥有的令牌 - 分配出去的令牌
this.storedPermits -= storedPermitsToSpend;
//用于计算睡觉时间
return returnValue;
}
5、 跟resync方法
代码语言:javascript复制void resync(long nowMicros) {
// 当前时间大于nextFreeTicketMicros,说明很久没人用了
if (nowMicros > nextFreeTicketMicros) {
//计算这个范围内生成的令牌个数
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
//设置令牌个数
storedPermits = min(maxPermits, storedPermits newPermits);
//设置nextFreeTicketMicros
nextFreeTicketMicros = nowMicros;
}
}
核心方法讲解(上面的四)
场景:每一秒产生1个令牌,然后线程A在0秒的申请了一个令牌,10秒以后B来了,要申请10个令牌,此时进入下面的方法
代码语言:javascript复制final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
//更新nextFreeTicketMicros=10秒
resync(nowMicros);
//用于计算要sleep的时间
long returnValue = nextFreeTicketMicros;
//此时你需要10个令牌,系统有1个令牌,允许分配给你1个令牌
double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
//还需要9个令牌
double freshPermits = requiredPermits - storedPermitsToSpend;
//还需要等待9个*1秒 = 9 秒
long waitMicros =
storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
(long) (freshPermits * stableIntervalMicros);
//更新nextFreeTicketMicros,加9秒,即为19秒
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
//拥有的令牌 - 分配出去的令牌
this.storedPermits -= storedPermitsToSpend;
//用于计算睡觉时间
return returnValue;
}
总结
1 这种懒加载计算的方法其实很常见,比如懒汉的单例模式,redis里的惰性删除
2 文章写的很烂,其实自己去跟源码才是最好的,别人写的都是转述,三人成虎,只有自己理解的才是最正确的。
3 本文只考虑了SmoothBursty,其实还有SmoothWarmingUp这种,我太菜了,没看懂。
源码下载
ChaiRongD/Demooo - Gitee.com