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
//创建一个一秒产生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个令牌
//每秒创建0.1个令牌,即10秒创建一个令牌 RateLimiter rateLimiter = RateLimiter.create(0.1);
其中permitsPersecond = 0.1
public static RateLimiter create(double permitsPerSecond) { return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
其中permitsPersecond = 0.1
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秒
SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) { super(stopwatch); this.maxBurstSeconds = maxBurstSeconds; }
1.2看rateLimiter.setRate(permitsPerSecond) 方法
此处用了synchronized关键字
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)
@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
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
@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表示我要获取一个 令牌
public double acquire(int permits) { long microsToWait = reserve(permits); stopwatch.sleepMicrosUninterruptibly(microsToWait); return 1.0 * microsToWait / SECONDS.toMicros(1L); }
2、往下跟,其中permits = 1,表示我要获取的一个令牌
final long reserve(int permits) { checkPermits(permits); synchronized (mutex()) { return reserveAndGetWaitLength(permits, stopwatch.readMicros()); } }
3、往下跟,其中permits = 1表示我要获取一个令牌,nowMicros表示此时此刻的时间
final long reserveAndGetWaitLength(int permits, long nowMicros) { long momentAvailable = reserveEarliestAvailable(permits, nowMicros); return max(momentAvailable - nowMicros, 0); }
4、往下跟,其中permits = 1表示我要获取一个令牌,nowMicros表示此时此刻的时间
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方法
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个令牌,此时进入下面的方法
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