清溪算法 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 );
}参考: