它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,经过它,突发流量能够被整形以便为网络提供一个稳定的流量。 漏桶能够看做是一个带有常量服务时间的单服务器队列,若是漏桶(包缓存)溢出,那么数据包会被丢弃。 简单的理解为:漏桶算法思路很简单,水(数据或者请求)先进入到漏桶里,漏桶以必定的速度出水,当水流入速度过大会直接溢出,能够看出漏桶算法能强行限制数据的传输速率。php
在桶满水以后,常见的两种处理方式为:html
1)暂时拦截住上方水的向下流动,等待桶中的一部分水漏走后,再放行上方水。java
2)溢出的上方水直接抛弃。python
将水看做网络通讯中数据包的抽象,则nginx
方式1起到的效果称为Traffic Shaping(流量整形),git
方式2起到的效果称为Traffic Policing(流量策略)。github
因而可知,Traffic Shaping 的核心理念是“等待”,Traffic Policing 的核心理念是“丢弃”。它们是两种常见的流速控制方法。redis
在某些状况下,漏桶算法不可以有效地使用网络资源。由于漏桶的漏出速率是固定的参数(恒定的速率往下漏水),因此,即便网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使某一个单独的流突发到端口速率。所以,漏桶算法对于存在突发特性的流量来讲缺少效率。而令牌桶算法则可以知足这些具备突发特性的流量。一般,漏桶算法与令牌桶算法能够结合起来为网络流量提供更大的控制。算法
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而若是请求须要被处理,则须要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。 令牌桶的另一个好处是能够方便的改变速度。 一旦须要提升速率,则按需提升放入桶中的令牌的速率。 通常会定时(好比100毫秒)往桶中增长必定数量的令牌, 有些变种算法则实时的计算应该增长的令牌的数量, 好比华为的专利"采用令牌漏桶进行报文限流的方法"(CN 1536815 A),提供了一种动态计算可用令牌数的方法, 相比其它定时增长令牌的方法, 它只在收到一个报文后,计算该报文与前一报文到来的时间间隔内向令牌漏桶内注入的令牌数, 并计算判断桶内的令牌数是否知足传送该报文的要求。后端
从整个架构的稳定性角度看,通常 SOA 架构的每一个接口在有限资源的状况下,所能提供的单位时间服务能力是有限的。假如超过服务能力,通常会形成整个接口服务停顿,或者应用 Crash(宕机),或者带来连锁反应,将延迟传递给服务调用方形成整个系统的服务能力丧失。有必要在服务能力超限的状况下快速失败(Fail Fast)。另外,根据排队论,因为 API 接口服务具备延迟随着请求量提高迅速提高的特色,为了保证 SLA 的低延迟,须要控制单位时间的请求量。这也是 Little’s law 所说的。
还有,公开 API 接口服务,速率限制(Rate limiting)应该是一个必备的功能,不然公开的接口不知道哪一天就会被服务调用方有意无心的打垮。因此,提供资源可以支撑的服务,将过载请求快速抛弃对整个系统架构的稳定性很是重要。这就要求在应用层实现 Rate limiting 限制。
Nginx 模块
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
location /search/ {
limit_req zone=one burst=5;
}
详细参见: ngx_http_limit_req_module
详细参见: Haproxy Rate limit 模块
这些策略可用于速率限制请求不一样的网站中,后端或 API 调用等场景。
这个在 Redis 官方文档有很是详细的实现。通常适用于全部类型的应用,好比 PHP、Python 等等。Redis 的实现方式能够支持分布式服务的访问频率的集中控制。Redis 的频率限制实现方式还适用于在应用中没法状态保存状态的场景。
Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法来完成限流,很是易于使用。RateLimiter常常用于限制对一些物理资源或者逻辑资源的访问速率.它支持两种获取许可令牌(permits)接口,一种是若是拿不到马上返回false,一种会阻塞等待一段时间看能不能拿到.
Eg:阻塞等待
//每秒建立五个令牌,即没200ms建立一个令牌 RateLimiter rateLimiter = RateLimiter.create(5.0); public static void main(String[] args) throws Exception { RetaLimiterLearn rll = new RetaLimiterLearn(); rll.oneTest(); } public void oneTest() { for (int i = 0; i < 5; i++) { //消耗一个令牌,线程会阻塞。 System.out.println(rateLimiter.acquire()); } }
若是当前桶中有足够令牌则成功(返回值为0),若是桶中没有令牌则暂停一段时间,好比发令牌间隔是200毫秒,则等待200毫秒后再去消费令牌,这种实现将突发请求速率平均为了固定请求速率。
再看一个例子:
public void twoTest() { //一次获取5个令牌 System.out.println(rateLimiter.acquire(5)); for (int i = 0; i < 5; i++) { System.out.println(rateLimiter.acquire()); } }
令牌桶算法容许必定程度的突发,因此能够一次性消费5个令牌,但接下来的limiter.acquire(1)将等待差很少1秒桶中才能有令牌,且接下来的请求也整形为固定速率了。
public void threeTest() throws Exception{ //建立了一个桶容量为2且每秒新增2个令牌 RateLimiter limiter = RateLimiter.create(2); //消费一个令牌,此时令牌桶能够知足(返回值为0) System.out.println(limiter.acquire()); //线程暂停2秒 Thread.sleep(2000L); for (int i = 0; i < 5; i++) { System.out.println(limiter.acquire()); } }
咱们发现到第四个acquire时就须要等待500毫秒了。此处能够看到咱们设置的桶容量为2(即容许的突发量),这是由于平滑突发限流(SmoothBursty)中有一个参数:最大突发秒数(maxBurstSeconds)默认值是1s,突发量/桶容量=速率*maxBurstSeconds,因此本示例桶容量/突发量为2,例子中前两个是消费了以前积攒的突发量,而第三个开始就是正常计算的了。令牌桶算法容许将一段时间内没有消费的令牌暂存到令牌桶中,留待将来使用,并容许将来请求的这种突发。
SmoothBursty经过平均速率和最后一次新增令牌的时间计算出下次新增令牌的时间的,另外须要一个桶暂存一段时间内没有使用的令牌(便可以突发的令牌数)。
RateLimiter还提供了tryAcquire方法来进行无阻塞或可超时的令牌消费。由于SmoothBursty容许必定程度的突发,会有人担忧若是容许这种突发,假设忽然间来了很大的流量,那么系统极可能扛不住这种突发。所以须要一种平滑速率的限流工具,从而系统冷启动后慢慢的趋于平均固定速率(即刚开始速率小一些,而后慢慢趋于咱们设置的固定速率)。Guava也提供了平滑预热限流(SmoothWarmingUp)来实现这种需求,其能够认为是漏桶算法,可是在某些特殊场景又不太同样。
SmoothWarmingUp建立方式:
RateLimiter.create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)
permitsPerSecond表示每秒新增的令牌数,warmupPeriod表示在从冷启动速率过渡到平均速率的时间间隔。
public void fourTest() throws InterruptedException { /** * permitsPerSecond表示每秒新增的令牌数 * warmupPeriod表示在从冷启动速率过渡到平均速率的时间间隔。 * unit 时间单位 毫秒 */ RateLimiter limiter = RateLimiter.create(5, 1000, TimeUnit.MILLISECONDS); for(int i = 1; i < 5;i++) { System.out.println(limiter.acquire()); } //线程暂停1秒 Thread.sleep(1000L); for(int i = 1; i < 5;i++) { System.out.println(limiter.acquire()); } }
tryAcquire()的用法:
If(limiter.tryAcquire()){ //未请求到limiter则当即返回false
doSomething();
}else{
doSomethingElse();
}
<dubbo:protocol name="dubbo" port="20881"threadpool="limited" threads="200"/>