清溪算法 2020-02-12
RateLimiter是guava提供的基于令牌桶算法的实现类,可以非常简单的完成限流特技,并且根据系统的实际情况来调整生成token的速率。
通常可应用于抢购限流防止冲垮系统;限制某接口、服务单位时间内的访问量,譬如一些第三方服务会对用户访问量进行限制;限制网速,单位时间内只允许上传下载多少字节等。
guava的maven依赖
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>25.1-jre</version> </dependency>
令牌桶的原理,有一个独立线程一直以一个固定的速率往桶中存放令牌,客户端去桶中获取令牌,获取到令牌,就可以访问,获取不到,说明请求过多,需要服务降级。
示例代码:
(1)请求限流注解
/** * @创建人: hadoop * @创建时间: 2020/2/12 * @描述: */ @Target(value = {ElementType.METHOD,ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface LimitingAnnotation { /** * 获取令牌超时时间 */ long timeOut() default 0L; /** * 限流速率,每秒最多产生令牌数 */ long produceRate() default 1000L; }
(2)请求限流切面
/** * @创建人: hadoop * @创建时间: 2020/2/12 * @描述: 服务限流降级切面,防止每秒请求数过多 */ @Component @Aspect @Slf4j public class LimitingAop { @Value("${request.limit}") private Integer limitValue ; @Autowired private HttpServletResponse response; // 令牌桶 // limitValue : 表示每秒中生成limitValue个令牌存放在桶中 @SuppressWarnings("UnstableApiUsage") private RateLimiter rateLimiter = RateLimiter.create(limitValue); /** * 限流切点 */ @Pointcut("@annotation(com.service.bussiness.aop.LimitingAnnotation)") public void limitingPointCut() { } @SuppressWarnings({"rawtypes", "UnstableApiUsage"}) @Around("limitingPointCut()") public Object controllerAround( ProceedingJoinPoint point ) { String description = CommonConst.EMPTYSTRING; Method method = null; String methodName = point.getSignature().getName(); Class[] paramTypes = ((MethodSignature) point.getSignature()).getParameterTypes(); try { method = point.getTarget().getClass().getMethod(methodName, paramTypes); if ( !method.isAnnotationPresent(LimitingAnnotation.class) ) { return null; } } catch ( NoSuchMethodException e ) { log.error("限流切面出现异常,异常原因是: " + e.getMessage()); throw new CustomException( Integer.parseInt(CustomExceptionType.SYSTEM_ERROR.getCode()) , e.getMessage(), "限流切面出现异常", "请求的目标方法不存在" ); } LimitingAnnotation limitingAnnotation = method.getAnnotation(LimitingAnnotation.class); if ( null == limitingAnnotation ){ return null; } long timeOut = limitingAnnotation.timeOut(); long produceRate = limitingAnnotation.produceRate(); // 设置限流速率,每秒产生多少令牌 rateLimiter.setRate(produceRate); // 每次发送请求,在设定ms内没有获取到令牌,则对服务进行降级处理 boolean acquire = rateLimiter.tryAcquire(timeOut, TimeUnit.MILLISECONDS); if ( !acquire ){ getErrorMsg(); return null; } try { return point.proceed(); } catch (Throwable throwable) { log.error(methodName+"请求出现异常,异常原因是: " + throwable.getMessage()); throwable.printStackTrace(); } return null; } /** * 向客户端输出服务降级信息 * */ public void getErrorMsg(){ response.setHeader("Content-Type","application/json;charset=UTF-8"); PrintWriter printWriter = null; try { printWriter = response.getWriter(); Map<String,Object> resultMap = new HashMap<>(); resultMap.put("msg","前方任务过多,请稍后再试"); printWriter.write(JSON.toJSONString(resultMap)); printWriter.flush(); } catch (IOException e) { e.printStackTrace(); } } }
(3) 请求服务
@RequestMapping(value = "/search", method = RequestMethod.POST, consumes = "application/json") @ResponseBody @LimitingAnnotation( timeOut = 500, produceRate = 1000 ) public ResponseEntity<String> search( @RequestBody String param ) {return this.searchService.searchBillInfo( xEncryption , params ); }
参考: