目前经常使用的限流算法有两个:漏桶算法和令牌桶算法。java
漏桶算法的原理比较简单,请求进入到漏桶中,漏桶以必定的速率漏水。当请求过多时,水直接溢出。能够看出,漏桶算法能够强制限制数据的传输速度。算法
令牌桶算法的原理是系统以必定速率向桶中放入令牌,若是有请求时,请求会从桶中取出令牌,若是能取到令牌,则能够继续完成请求,不然等待或者拒绝服务。这种算法能够应对突发程序的请求,所以比漏桶算法好。缓存
在Wikipedia上,令牌桶算法是这么描述的:并发
Guava中开源出来一个令牌桶算法的工具类RateLimiter,能够轻松实现限流的工做。RateLimiter对简单的令牌桶算法作了一些工程上的优化,具体的实现是SmoothBursty。须要注意的是,RateLimiter的另外一个实现SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。也许是出于简单起见,RateLimiter中的时间窗口能且仅能为1S,若是想搞其余时间单位的限流,只能另外造轮子。app
RateLimiter有一个有趣的特性是[前人挖坑后人跳],也就是说RateLimiter容许某次请求拿走了超出剩余令牌数的令牌,可是下一次请求将为此付出代价,一直等到令牌亏空补上,而且桶中有足够本次请求使用的令牌为止。这里面就涉及到一个权衡,是让前一次请求干等到令牌够用才走掉呢,仍是让它走掉后面的请求等一等呢?Guava的设计者选择的是后者,先把眼前的活干了,后面的过后面再说。工具
测试代码:测试
public class RateLimiterMain { public static void main(String[] args) { RateLimiter rateLimiter = RateLimiter.create(2); System.out.println(rateLimiter.acquire(5)); System.out.println(rateLimiter.acquire(2)); System.out.println(rateLimiter.acquire(1)); } }
输出内容:优化
0.0 2.496889 0.992149
能够看出,令牌桶每秒只能产生2个令牌,咱们能够第一次取出5个,可是第二次再去取令牌的时候,须要等2.5s,也就是第一次令牌取完后,须要等2.5s才能取到令牌。一样的,第三次取1个令牌的时候,也须要等待第二次的1s的时间。也就是,取的速率能够超过令牌产生的速率,可是下一次再次去取的时候,须要阻塞等待。ui
固然也可使用tryAcquire来非阻塞的获取,能够实时返回结果。另外tryAcquire也能够传入参数,也就是等待的时间,超时直接返回false。这点等同于常见的lock,tryLock。google
通常来讲,在网关系统中,还有一个参数叫并发控制,就是某一个资源能够被同时访问的个数。这种状况下,咱们可使用Semaphore来控制。
Semaphore不一样于互斥锁。互斥锁是某个资源只能支持同时一个访问,而Semaphore能够支持多个访问,可是加上了总数的控制。
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>18.0</version> </dependency>
把限流服务封装到一个类中AccessLimitService,提供tryAcquire()方法,用来尝试获取令牌,返回true表示获取到,以下所示:
@Service public class AccessLimitService { //每秒只发出5个令牌 RateLimiter rateLimiter = RateLimiter.create(5.0); /** * 尝试获取令牌 * @return */ public boolean tryAcquire(){ return rateLimiter.tryAcquire(); } }
调用方是个普通的controller,每次收到请求的时候都尝试去获取令牌,获取成功和失败打印不一样的信息,以下:
@Controller public class HelloController { private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Autowired private AccessLimitService accessLimitService; @RequestMapping("/access") @ResponseBody public String access(){ //尝试获取令牌 if(accessLimitService.tryAcquire()){ //模拟业务执行500毫秒 try { Thread.sleep(500); }catch (InterruptedException e){ e.printStackTrace(); } return "aceess success [" + sdf.format(new Date()) + "]"; }else{ return "aceess limit [" + sdf.format(new Date()) + "]"; } } }