Guava中的RateLimiter可以限制单进程中某个方法的速率,本文主要介绍如何使用,实现原理请参考文档:推荐:超详细的Guava RateLimiter限流原理解析和推荐:RateLimiter 源码分析(Guava 和 Sentinel 实现)。
1 基于spring-mvc的controller测试限流
完整代码可参考:https://github.com/sxpujs/spring-cloud-examples/tree/master/rest-service
1.1 增加Maven依赖:
1<dependency> 2 <groupId>com.google.guava</groupId> 3 <artifactId>guava</artifactId> 4 <version>29.0-jre</version> 5</dependency>
1.2 AccessLimitService 限流Service类
1@Service 2public class AccessLimitService { 3 4 // 每秒发出5个令牌 5 RateLimiter rateLimiter = RateLimiter.create(5.0); 6 7 /** 8 * 尝试获取令牌 9 */ 10 public boolean tryAcquire() { 11 return rateLimiter.tryAcquire(); 12 } 13}
1.3 控制器类
1@RestController 2@Slf4j 3public class HelloController { 4 5 @Autowired 6 private AccessLimitService accessLimitService; 7 8 @RequestMapping("/access") 9 public String access() { 10 if (accessLimitService.tryAcquire()) { 11 log.info("start"); 12 // 模拟业务执行500毫秒 13 try { 14 Thread.sleep(500); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 return "access success [" + LocalDateTime.now() + "]"; 19 } else { 20 //log.warn("限流"); 21 return "access limit [" + LocalDateTime.now() + "]"; 22 } 23 } 24}
1.4 使用wrk工具模拟客户端发起多个请求
我们使用HTTP基准工具wrk来生成大量HTTP请求。在终端输入如下命令来测试:
wrk -t1 -c10 -d2s http://127.0.0.1:8080/access
服务端日志如下所示(稍做简化),可以看出前6行的执行时间是一样的,这是因为RateLimiter的默认实现SmoothBursty会缓存1秒的许可,在定义RateLimiter实例时,每秒5个许可,加上新占用的1个许可,一共有6个。从第7行开始,每0.2秒执行1次,符合预期。
12020-07-05 15:46:16.605 INFO --- [nio-8080-exec-2] HelloController : start 22020-07-05 15:46:16.605 INFO --- [nio-8080-exec-3] HelloController : start 32020-07-05 15:46:16.605 INFO --- [nio-8080-exec-7] HelloController : start 42020-07-05 15:46:16.605 INFO --- [nio-8080-exec-8] HelloController : start 52020-07-05 15:46:16.605 INFO --- [nio-8080-exec-9] HelloController : start 62020-07-05 15:46:16.605 INFO --- [nio-8080-exec-4] HelloController : start 72020-07-05 15:46:16.804 INFO --- [nio-8080-exec-1] HelloController : start 82020-07-05 15:46:17.005 INFO --- [io-8080-exec-11] HelloController : start 92020-07-05 15:46:17.204 INFO --- [nio-8080-exec-9] HelloController : start 102020-07-05 15:46:17.404 INFO --- [nio-8080-exec-5] HelloController : start 112020-07-05 15:46:17.604 INFO --- [nio-8080-exec-8] HelloController : start 122020-07-05 15:46:17.804 INFO --- [nio-8080-exec-2] HelloController : start 132020-07-05 15:46:18.004 INFO --- [nio-8080-exec-7] HelloController : start 142020-07-05 15:46:18.204 INFO --- [nio-8080-exec-6] HelloController : start 152020-07-05 15:46:18.404 INFO --- [nio-8080-exec-5] HelloController : start
2 基于单个类的main方法测试限流
1package com.demo.guava; 2 3import com.google.common.util.concurrent.RateLimiter; 4import lombok.extern.slf4j.Slf4j; 5 6import java.util.concurrent.ExecutorService; 7import java.util.concurrent.Executors; 8import java.util.stream.IntStream; 9 10@Slf4j 11public class RateLimiterDemo { 12 13 static void submitTasks1() { 14 ExecutorService pool = Executors.newFixedThreadPool(10); 15 RateLimiter rateLimiter = RateLimiter.create(5); // rate is "5 permits per second" 16 IntStream.range(0, 10).forEach(i -> pool.submit(() -> { 17 if (rateLimiter.tryAcquire()) { 18 try { 19 log.info("start"); 20 Thread.sleep(500); 21 } catch (InterruptedException e) { 22 } 23 } else { 24 log.warn("限流"); 25 } 26 })); 27 pool.shutdown(); 28 /* 2916:18:18.784 [pool-1-thread-1] INFO RateLimiterDemo - start 3016:18:18.784 [pool-1-thread-7] WARN RateLimiterDemo - 限流 3116:18:18.784 [pool-1-thread-2] WARN RateLimiterDemo - 限流 3216:18:18.784 [pool-1-thread-4] WARN RateLimiterDemo - 限流 3316:18:18.784 [pool-1-thread-5] WARN RateLimiterDemo - 限流 3416:18:18.784 [pool-1-thread-6] WARN RateLimiterDemo - 限流 3516:18:18.784 [pool-1-thread-9] WARN RateLimiterDemo - 限流 3616:18:18.784 [pool-1-thread-3] WARN RateLimiterDemo - 限流 3716:18:18.784 [pool-1-thread-10] WARN RateLimiterDemo - 限流 3816:18:18.784 [pool-1-thread-8] WARN RateLimiterDemo - 限流 39 */ 40 } 41 42 static void submitTasks2() { 43 ExecutorService pool = Executors.newFixedThreadPool(10); 44 RateLimiter rateLimiter = RateLimiter.create(5); // rate is "5 permits per second" 45 IntStream.range(0, 10).forEach(i -> pool.submit(() -> { 46 rateLimiter.acquire(); 47 log.info("start"); 48 try { 49 Thread.sleep(500); 50 } catch (InterruptedException e) { 51 e.printStackTrace(); 52 } 53 })); 54 pool.shutdown(); 55 /* 5616:18:56.030 [pool-1-thread-1] INFO RateLimiterDemo - start 5716:18:56.227 [pool-1-thread-10] INFO RateLimiterDemo - start 5816:18:56.428 [pool-1-thread-9] INFO RateLimiterDemo - start 5916:18:56.627 [pool-1-thread-8] INFO RateLimiterDemo - start 6016:18:56.827 [pool-1-thread-7] INFO RateLimiterDemo - start 6116:18:57.028 [pool-1-thread-6] INFO RateLimiterDemo - start 6216:18:57.226 [pool-1-thread-5] INFO RateLimiterDemo - start 6316:18:57.426 [pool-1-thread-4] INFO RateLimiterDemo - start 6416:18:57.629 [pool-1-thread-3] INFO RateLimiterDemo - start 6516:18:57.826 [pool-1-thread-2] INFO RateLimiterDemo - start 66 */ 67 } 68 69 static void submitTasks3() { 70 RateLimiter r = RateLimiter.create(5); 71 log.info("start"); 72 for (;;) { 73 log.info("get 1 tokens: " + r.acquire() + "s"); 74 } 75 /* 7616:15:46.310 [main] INFO RateLimiterDemo - start 7716:15:46.315 [main] INFO RateLimiterDemo - get 1 tokens: 0.0s 7816:15:46.513 [main] INFO RateLimiterDemo - get 1 tokens: 0.193752s 7916:15:46.709 [main] INFO RateLimiterDemo - get 1 tokens: 0.194875s 8016:15:46.911 [main] INFO RateLimiterDemo - get 1 tokens: 0.199033s 8116:15:47.113 [main] INFO RateLimiterDemo - get 1 tokens: 0.197833s 8216:15:47.312 [main] INFO RateLimiterDemo - get 1 tokens: 0.195898s 83 */ 84 } 85 86 static void submitTasks4() { 87 RateLimiter r = RateLimiter.create(5); 88 log.info("start"); 89 for (;;) { 90 if (r.tryAcquire()) { 91 log.info("run"); 92 } 93 } 94 /* 9516:17:17.098 [main] INFO RateLimiterDemo - start 9616:17:17.100 [main] INFO RateLimiterDemo - run 9716:17:17.296 [main] INFO RateLimiterDemo - run 9816:17:17.496 [main] INFO RateLimiterDemo - run 9916:17:17.696 [main] INFO RateLimiterDemo - run 100 */ 101 } 102 103 public static void main(String[] args) throws InterruptedException { 104 //submitTasks1(); 105 submitTasks2(); 106 //submitTasks3(); 107 //submitTasks4(); 108 } 109}
参考文档: