通常系统为了防止服务被调用的QPS超出其承载能力,防止大流量压垮服务器形成雪崩后果,设计时每每会加入限流的逻辑。经过限流,当请求超出系统的服务能力时,系统能够采起拒绝服务/排队等待/降级等策略保护自身。git
原理:单位时间内计数,对请求计数,当超过设定的限流值,系统对请求采起限流策略;当单位时间结束时,计数器清零,从新开始计数;github
优势:实现简单,容易理解;算法
缺点:不能实现均衡的限流,好比在单位时间快结束时发起大量请求,并在下一次单位时间刚开始发起大量请求,这样就会对系统请求形成连续两个峰值,有可能压垮系统,不能有效的把请求均摊;缓存
若是单位时间越小,限流的精度就会越高。服务器
原理:请求像一个水管里的水流同样注入到一个漏桶里,漏桶以恒定的速度往外漏水(不管进入的桶里的水流速度是多少);当桶满了,水流则会溢出;这样当流量再大也会以恒定的速度调用后台服务,或者低于设定的速度(当请求流量不足时);工具
优势:漏桶能够起到削峰,平滑请求的做用,ui
缺点:不能有效的利用系统资源,即时在系统可乘受住的峰值进入时,也不能加快处理请求,总体上会放缓系统对请求的处理速度。this
原理:令牌桶是一个桶能够容纳有限的令牌,令牌是以固定的速度往令牌桶里不停的放,若是令牌桶满了,令牌着放不进去溢出;当有请求时,根据请求量去取相应的令牌数;google
优势:相比漏桶,令牌桶容许必定的突发流量,请求空闲时预热一部分令牌,新请求进来时无需等待。.net
缺点:代码实现相对复杂一些。
RateLimiter是Guava工具包提供的限流工具,采用令牌桶算法实现。 RateLimiter经过配置固定的rate参数,来以恒定的速度分发许可。同时RateLimiter提供了预热模式来分发许可,逐步达到配置的rate速度(好比在系统缓存尚未预热好时,过多的请求会给系统带来过大压力,预热模式能够很好的解决这个问题)。若是RateLimiter在预热阶段内,不被调用了,它会逐渐的回到冷却状态,再次调用时,在像刚开始同样从新预热。RateLimiter提供了acquire(阻塞)和tryAcquire(非阻塞)两种模式获取许可。
建立实例接口
获取permits
请求令牌数量不影响请求自己限流的效果(若是acquire(1)和acquire(1000)触发了限流,对该请求限流结果是同样的),它只会影响接下来请求的限流效果,好比若是上一次请求开销很大,拿去了不少的permits,那接下来的高开销请求可能会被限制。
有一些任务要执行,假设每秒最多不能执行2个以上的任务。
final RateLimiter rateLimiter = RateLimiter.create(2.0); // rate is "2 permits per second" void submitTasks(List<Runnable> tasks, Executor executor) { for (Runnable task : tasks) { rateLimiter.acquire(); // may wait executor.execute(task); } }
* 另一个例子,假设咱们生产数据流,想以5kb/s 的速度进行发送,能够采用下面的方式实现。
final RateLimiter rateLimiter = RateLimiter.create(5000.0); // rate = 5000 permits per second void submitPacket(byte[] packet) { rateLimiter.acquire(packet.length); networkService.send(packet); }
### RateLimiter实现原理 这里经过对RateLimiter扩展实现类的SmoothRateLimiter注解进行翻译加上我的理解,逐步讲述RateLimiter设计实现。 假定对于实现固定QPS的访问速度限定,能够经过记录上一次请求受权的timestamp,而后当1/QPS时间度事后分发一个permit。例如,固定速率 QPS=5,若是当上一个请求过去不到200ms,确保新请求不被受权,咱们就能够达到想要的限定rate。 RateLimiter对过去的记录不多,假设它只记录了上一次请求。若是RateLimiter长时间没有被使用,新的请求到达时,可否当即被受权?RateLimiter将忘掉过去的未使用的受权。依照现实世界,当使用没有达到设定的rate时,perimits将会利用率不足,或者溢出。过去的利用不足(past underutilization)意味着过量的资源是可用的,那么RateLimiter能够经过加速一会来充分利用这些资源。例如当限定bandwidth时,过去资源利用不足意味着几乎为空的buffers能够经过加速瞬速被填满。过去利用不足意味着服务器可能对处理将来新的请求准备不充分,好比服务器的缓存失效,或者新请求可能触发开销比较大的操做。 为了应对这些场景,RateLimiter添加了一个额外的维度,把过去的利用不足(past underutilization)设为 storedPermits变量。当没有利用不足时,storedPermits为0,若是有充分的利用不足发生时,storedPermits逐渐增长到maxStoredPermits,当有新新请求调用acquire(permits)时,若是storedPermits>permits,则直接返还回对应的permits(这里在预热模式实现会不一样,预热模式一样会让请求等待),若是不足,则不足部分等到新的permits。 经过下边的例子解释这种工做机制: 例如RateLimiter每秒产生一个token,若是接下来RateLimiter没有被利用,那么咱们把storedPermits加1,假设咱们10s没有用RateLimiter,那么storedPermits将为10(设定maxStoredPermits >= 10.0)。这时一个acquire(3)的请求到达,咱们直接从storedPermits中取出3个来服务,瞬间这时有个acquire(10)的请求到达,咱们能够经过storedPermits中取出剩余的7个,而后剩下的3个经过RateLimiter新的生产出3个permits来服务。 咱们已经知道分发3个新permits将会花费3秒,那么如何分发7个stored permits?这个没有统一的答案。若是咱们但愿处理利用不足问题,那么咱们可能会把更快的或者不让请求等待直接从storedPermits中取出,若是咱们担忧资源使用过分或者预热不足,咱们能够放慢分发storedPermits,所以咱们须要一个function把storedPermits转换为限流时间。 这个function 在RateLimiter的扩展类SmoothRateLimiter中是storedPermitsToWaitTime(double storedPermits, double permitsToTake),这个function把storedPermits转换为等待时间。这个在SmoothBursty和SmoothWarmingUp是不一样的实现,SmoothBursty直接返回无需等待,SmoothWarmingUp则要计数出等待时间。 最后一点是,若是RateLimiter 采用QPS=1的限定速度,那么开销较大的acquire(100)请求到达时,它是不必等到100s 才开始实际任务。咱们能够先开始任务执行,并把将来的请求推后100s,这样咱们就能够同时工做,同时生产须要的permits,而不是空等待permits生产够才开始工做。这样的话那么RateLimiter不须要记住time of the _last_ request,它只须要记住 the (expected) time of the _next_ request。咱们经过 (now - the expected time of the _next_ request )计算出为利用的时间段t,经过t*QPS计算出storedPermits。这个方法就是 SmoothRateLimiter 中的reserveEarliestAvailable方法。 ### 关键方法分析 * SmoothRateLimiter中的 reserveEarliestAvailable
//该方法计算知足当前requiredPermits数量的“时刻” 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; }
void resync(long nowMicros) { // if nextFreeTicket is in the past, resync to now //nextFreeTicketMicros 是上文中咱们讲的the expected time of the _next_ request if (nowMicros > nextFreeTicketMicros) { double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros(); storedPermits = min(maxPermits, storedPermits + newPermits); nextFreeTicketMicros = nowMicros; }
}
//非预热模式 SmoothBursty double coolDownIntervalMicros() { // 等价于 1/QPS return stableIntervalMicros; }
* SmoothWarmingUp中的storedPermitsToWaitTime
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) { double availablePermitsAboveThreshold = storedPermits - thresholdPermits; long micros = 0; // measuring the integral on the right part of the function (the climbing line) if (availablePermitsAboveThreshold > 0.0) { double permitsAboveThresholdToTake = min(availablePermitsAboveThreshold, permitsToTake); // TODO(cpovirk): Figure out a good name for this variable. double length = permitsToTime(availablePermitsAboveThreshold) + permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake); micros = (long) (permitsAboveThresholdToTake * length / 2.0); permitsToTake -= permitsAboveThresholdToTake; } // measuring the integral on the left part of the function (the horizontal line) micros += (stableIntervalMicros * permitsToTake); return micros; }
 方法中设计到的参数计算方式这里不细讲了。 [https://my.oschina.net/robinyao/blog/790890](https://my.oschina.net/robinyao/blog/790890)