为什么要限流
在调用一些第三方的接口时,他们会有一些调用频率的限制,比如每秒不能超过多少次,这种时候,就需要用到限流的工具。
定义
还是给一个定义出来:
在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流
- 缓存 缓存的目的是提升系统访问速度和增大系统处理容量
- 降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开
- 限流 限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理
如何限流
傻瓜方案
因为我们需要限流的地方是对第三方调用的时候,我们是在循环里调用的第三方,所以,简单粗暴的Thread.sleep(500) 其实也可以用。
正式方案
Guava的RateLimiter算是比较方便的限流工具。
Guava RateLimiter基于令牌桶算法,我们只需要告诉RateLimiter系统限制的QPS是多少,那么RateLimiter将以这个速度往桶里面放入令牌,然后请求的时候,通过acquire()方法向RateLimiter获取许可(令牌)。
来个例子
public static void main(String[] args) { // qps设置为2,代表一秒钟只允许处理2个并发请求 RateLimiter rateLimiter = RateLimiter.create(2); StopWatch sw=new StopWatch(); sw.start(); int nTasks = 10; for (int i = 0; i < nTasks; i++) { rateLimiter.acquire(1); sw.split(); System.out.println("job id is "+i+"sw: "+sw.getSplitTime()); //sw.shortSummary(); } sw.stop(); System.out.println("finish,total time is "+sw.getTime()); }
输出是这样的:
job id is 0sw: 0 job id is 1sw: 497 job id is 2sw: 998 job id is 3sw: 1499 job id is 4sw: 2002 job id is 5sw: 2498 job id is 6sw: 2997 job id is 7sw: 3497 job id is 8sw: 3998 job id is 9sw: 4502 finish,total time is 4502
注意
有一点需要注意,RateLimiter的这个令牌是会累积的,也就是说,就算令牌不使用,也会积攒下来,当需要使用的时候,一股脑的给出去。
RateLimiter rateLimiter = RateLimiter.create(2); //追一下代码 public static RateLimiter create(double permitsPerSecond) { return create(permitsPerSecond, RateLimiter.SleepingStopwatch.createFromSystemTimer()); } @VisibleForTesting static RateLimiter create(double permitsPerSecond, RateLimiter.SleepingStopwatch stopwatch) { RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0D); rateLimiter.setRate(permitsPerSecond); return rateLimiter; }
可以看到,默认情况下,最多累积1s钟的令牌。
测试一下,代码:
@SneakyThrows public static void main(String[] args) { // qps设置为2,代表一秒钟只允许处理2个并发请求 RateLimiter rateLimiter = RateLimiter.create(2); StopWatch sw=new StopWatch(); sw.start(); int nTasks = 10; Thread.sleep(3000); for (int i = 0; i < nTasks; i++) { rateLimiter.acquire(1); sw.split(); System.out.println("job id is "+i+"sw: "+sw.getSplitTime()); //sw.shortSummary(); } sw.stop(); System.out.println("finish,total time is "+sw.getTime()); }
结果:
job id is 0sw: 3009 job id is 1sw: 3010 job id is 2sw: 3010 job id is 3sw: 3509 job id is 4sw: 4009 job id is 5sw: 4510 job id is 6sw: 5008 job id is 7sw: 5506 job id is 8sw: 6007 job id is 9sw: 6508 finish,total time is 6508
如果你想多累积几秒,那么创建的时候应该是:
RateLimiter rateLimiter = RateLimiter.create(2); rateLimiter.setRate(3D);