RateLimiter:做为抽象类提供一个限流器的基本的抽象方法。
SmoothRateLimiter:平滑限流器实现,提供了Ratelimiter中的抽象限流方法的平滑实现。
SmoothBursty:容许突发流量的平滑限流器的实现。
SmoothWarmingUp:平滑预热限流器的实现。函数
public void test() throws InterruptedException { RateLimiter rateLimiter = RateLimiter.create(2); while (true){ System.out.println(rateLimiter.acquire(2)); TimeUnit.SECONDS.sleep(2); System.out.println(rateLimiter.acquire(1)); System.out.println(rateLimiter.acquire(1)); System.out.println(rateLimiter.acquire(10)); } }
执行结果ui
acquire方法返回结果表明获取token所等待的时间。this
第一行0等待,刚建立限流器,还没来得及听任何token,此处存储的token=0,可是无欠款因此预消费2个;
sleep 2秒,按照每秒2个的速度,先“还”了欠款,而后token直接恢复至max = 2;
第二行0,现有2个token,用一个,无需等待。
第三行0,现有1个token,用一个,无需等待。
第四行0,现有0个token,无欠款,直接借10个。
第五行4.999868,帮上一个还欠款,等待5秒直到还完欠款后,又借了2个。
重复第一行......spa
在使用RateLimiter.create(double)方法初始化限流器时,实际上默认使用的是SmoothBurstypwa
public static RateLimiter create(double permitsPerSecond) { return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer()); } static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) { RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */); rateLimiter.setRate(permitsPerSecond); return rateLimiter; }
/** The currently stored permits. 当前存储的令牌数 */ double storedPermits; /** * The maximum number of stored permits. * 最大存储令牌数 */ double maxPermits; /** * The interval between two unit requests, at our stable rate. E.g., a stable rate of 5 permits * per second has a stable interval of 200ms. * 添加令牌时间间隔 */ double stableIntervalMicros; /** * The time when the next request (no matter its size) will be granted. After granting a request, * this is pushed further in the future. Large requests push this further than small requests. * 下一次请求能够获取令牌的起始时间 * 因为RateLimiter容许预消费,上次请求预消费令牌后 * 下次请求须要等待相应的时间到nextFreeTicketMicros时刻才能够获取令牌 */ private long nextFreeTicketMicros = 0L; // could be either in the past or future
从acquire()函数开始线程
public double acquire() { return acquire(1);//默认取一个令牌 } public double acquire(int permits) { long microsToWait = reserve(permits);//从限流器中获取指定的令牌,并返回须要等待的时间 stopwatch.sleepMicrosUninterruptibly(microsToWait);//让“闹钟”将当前线程中止睡眠指定时间 return 1.0 * microsToWait / SECONDS.toMicros(1L);//返回等待的时间,单位是秒 } final long reserve(int permits) { checkPermits(permits);//参数校验 synchronized (mutex()) {//获取锁,多个请求到达,须要串行的获取 return reserveAndGetWaitLength(permits, stopwatch.readMicros()); } }
先来看下加锁的逻辑code
private volatile Object mutexDoNotUseDirectly; private Object mutex() { Object mutex = mutexDoNotUseDirectly; if (mutex == null) { synchronized (this) { mutex = mutexDoNotUseDirectly; if (mutex == null) { mutexDoNotUseDirectly = mutex = new Object(); } } } return mutex; }
典型的双重检查单例
接着继续探索获取令牌的逻辑代码orm
final long reserveAndGetWaitLength(int permits, long nowMicros) { long momentAvailable = reserveEarliestAvailable(permits, nowMicros);//获取token并返回下个请求能够来获取token的时间 return max(momentAvailable - nowMicros, 0);//计算等待时间 }
关键函数一:
abstract long reserveEarliestAvailable(int permits, long nowMicros);
SmoothRateLimiter实现了它,是获取token、消耗token的主流程token
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; System.out.println(String.format("storedPermitsToSpend=%s,freshPermits=%s,waitMicros=%s,storedPermits=%s", storedPermitsToSpend, freshPermits, waitMicros, storedPermits)); return returnValue; }
storedPermitsToSpend表明须要从storedPermits扣除的token,若是storedPermits已经=0了,那么不会扣除到负数
waitMicros表明这次预消费的令牌须要多少时间来恢复,最终将它加到nextFreeTicketMicros**
那么SmoothBursty是怎么实现预消费的呢?,让咱们先看下更新token的逻辑,即void resync(long 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是怎么实现预消费的呢?
其实,只要保证一点就能够进行预消费,即无欠款,无欠款就表明当前时间大于等于nextFreeTime,SmoothBursty就是依靠此原理来处理突发的流量。
先看下示例代码
public void test_warmingUp(){ RateLimiter rateLimiter = RateLimiter.create(2, 4, TimeUnit.SECONDS); while (true){ System.out.println(rateLimiter.acquire(1)); System.out.println(rateLimiter.acquire(1)); System.out.println(rateLimiter.acquire(1)); System.out.println(rateLimiter.acquire(1)); } }
运行效果
0.0 1.372264 1.117788 0.869905 0.620505 0.496059 0.495301 0.496027 0.495794
SmoothWarmingUp为了预防忽然暴增的流量将系统压垮,很贴心的增长了“预热”。指定的warmupPeriod就是预热时间,在“冷状态”即没有流量进入时,放入每一个token的时间不单单是1/permitsPerSecond,还要加上一个预热时间,类注释上的图做了很好的解释。
SmoothWarmingUp在初始阶段与SmoothBursty有点不一样,SmoothWarmingUp初始storePermits = maxPermits。一直使用permits直至storePermits减小到thresholdPermits(setRate调用时计算)放入token的时间便稳定下来,到达了“热状态”,此时与SmoothBursty是如出一辙。可是若是在warmupPeriod时间内没有流量进入,则再次会进入“冷状态“。
在实现上SmoothWarmingUp与SmoothBursty基本相同,惟一不一样仅仅只有两个函数的实现上
SmoothWarmingUp的注释解释的很到位,在预热限流器中,计算token的等待时间就能够转化计算图中的面积,你们能够顺着注释推导一下。
SmoothBursty:初始token为0,容许预消费,放入token的时间固定为1/permitsPerSecond.(一开始直接上)
SmoothWarmingUp:初始token为MaxPermits,容许预消费,能够指定预热时间,在与预热时间事后速率恢复平稳与SmoothBursty一致。(老司机有前戏)
SmoothWarmingUp像了改良版的SmoothBursty,有个预热时间,系统能更加从容的应付流量的来袭,所以通常能够优先使用SmoothWarmingUp。