<!-- guava 限流 --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>25.1-jre</version> </dependency>
@Target({ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ServiceLimit { String description() default ""; }
@Component @Scope @Aspect public class LimitAspect { 每秒只发出5个令牌,此处是单进程服务的限流,内部采用令牌捅算法实现 private static RateLimiter rateLimiter = RateLimiter.create(5.0); //Service层切点 限流 @Pointcut("@annotation(com.itstyle.seckill.common.aop.ServiceLimit)") public void ServiceAspect() { } @Around("ServiceAspect()") public Object around(ProceedingJoinPoint joinPoint) { Boolean flag = rateLimiter.tryAcquire(); Object obj = null; try { if(flag){ obj = joinPoint.proceed(); } } catch (Throwable e) { e.printStackTrace(); } return obj; } }
@Override @ServiceLimit @Transactional public Result startSeckil(long seckillId,long userId) { //todo 操作 }
令牌桶和漏桶
漏桶演算法的實作往往依賴佇列,請求到達如果佇列未滿則直接放入佇列,然後有一個處理器依照固定頻率從佇列頭取出請求進行處理。如果請求量大,則會導致佇列滿,那麼新來的請求就會被拋棄。
令牌桶演算法 則是一個存放固定容量令牌的桶,依照固定速率在桶中加入令牌。桶中存放的令牌數有最大上限,超出之後就被丟棄或拒絕。當流量或網路請求到達時,每個請求都要取得一個令牌,如果能夠取得到,則直接處理,並且令牌桶刪除一個令牌。如果取得不到,該請求就要被限流,要麼直接丟棄,要麼在緩衝區等待。
令牌桶與漏桶比較
#令牌桶是依照固定速率在桶中加入令牌,請求是否被處理需要看桶中令牌是否足夠,當令牌數減為零時則拒絕新的請求;漏桶則是按照常數固定速率流出請求,流入請求速率任意,當流入的請求數累積到漏桶容量時,則新流入的請求被拒絕;
令牌桶限制的是平均流入速率,允許突發請求,只要有令牌就可以處理,支持一次拿3個令牌,4個令牌;漏桶限制的是常量流出速率,即流出速率是一個固定常數值,例如都是1的速率流出,而不能一次是1,下次又是2,從而平滑突發流入速率;
令牌桶允許一定程度的突發,而漏桶主要目的是平滑流出速率;
1.依賴
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>28.1-jre</version> <optional>true</optional> </dependency>
2.範例程式碼
@Slf4j @Configuration public class RequestInterceptor implements HandlerInterceptor { // 根据字符串分不同的令牌桶, 每天自动清理缓存 private static LoadingCache<String, RateLimiter> cachesRateLimiter = CacheBuilder.newBuilder() .maximumSize(1000) //设置缓存个数 /** * expireAfterWrite是在指定项在一定时间内没有创建/覆盖时,会移除该key,下次取的时候从loading中取 * expireAfterAccess是指定项在一定时间内没有读写,会移除该key,下次取的时候从loading中取 * refreshAfterWrite是在指定时间内没有被创建/覆盖,则指定时间过后,再次访问时,会去刷新该缓存,在新值没有到来之前,始终返回旧值 * 跟expire的区别是,指定时间过后,expire是remove该key,下次访问是同步去获取返回新值; * 而refresh则是指定时间后,不会remove该key,下次访问会触发刷新,新值没有回来时返回旧值 */ .expireAfterAccess(1, TimeUnit.HOURS) .build(new CacheLoader<String, RateLimiter>() { @Override public RateLimiter load(String key) throws Exception { // 新的字符串初始化 (限流每秒2个令牌响应) return RateLimiter.create(2); } }); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("request请求地址path[{}] uri[{}]", request.getServletPath(), request.getRequestURI()); try { String str = "hello"; // 令牌桶 RateLimiter rateLimiter = cachesRateLimiter.get(str); if (!rateLimiter.tryAcquire()) { System.out.println("too many requests."); return false; } } catch (Exception e) { // 解决拦截器的异常,全局异常处理器捕获不到的问题 request.setAttribute("exception", e); request.getRequestDispatcher("/error").forward(request, response); } return true; } }
3.測試
#@RestController @RequestMapping(value = "user") public class UserController { @GetMapping public Result test2(){ System.out.println("1111"); return new Result(true,200,""); } }
http://localhost:8080/user/
如果沒有result類,自己可以隨便回傳個字串
4.測試結果
##其他創建
#RateLimiter提供了兩個工廠方法:一個是平滑突發限流RateLimiter r = RateLimiter.create(5); //项目启动,直接允许5个令牌一個是平滑預熱限流
RateLimiter r = RateLimiter.create(2, 3, TimeUnit.SECONDS); //项目启动后3秒后才会到达设置的2个令牌
#缺點
RateLimiter只能用於單機的限流,如果想要集群限流,則需要引入redis或阿里開源的sentinel中間件。TimeUnit.SECONDS);` //项目启动后3秒后才会到达设置的2个令牌
以上是SpringBoot怎麼使用RateLimiter透過AOP方式進行限流的詳細內容。更多資訊請關注PHP中文網其他相關文章!