Guava RateLimiter 实现 API 限流,为每个方法设置不同的限流
程序员文章站
2024-01-09 18:29:16
...
Guava提供的RateLimiter可以限制物理或逻辑资源的被访问速率,咋一听有点像java并发包下的Samephore,但是又不相同,RateLimiter控制的是速率,Samephore控制的是并发量。
RateLimiter的原理类似于令牌桶,它主要由许可发出的速率来定义,如果没有额外的配置,许可证将按每秒许可证规定的固定速度分配,许可将被平滑地分发,若请求超过permitsPerSecond则RateLimiter按照每秒 1/permitsPerSecond 的速率释放许可。
引入依赖:
maven
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
- 简单使用
public static void main(String[] args) {
String start = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
RateLimiter limiter = RateLimiter.create(1.0); // 这里的1表示每秒允许处理的量为1个
for (int i = 1; i <= 10; i++) {
limiter.acquire();// 请求RateLimiter, 超过permits会被阻塞
System.out.println("call execute.." + i);
}
String end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println("start time:" + start);
System.out.println("end time:" + end);
}
- 实际项目中使用
service
@Service
public class GuavaRateLimiterService {
/*每秒控制5个许可*/
RateLimiter rateLimiter = RateLimiter.create(5.0);
/**
* 获取令牌
*
* @return
*/
public boolean tryAcquire() {
return rateLimiter.tryAcquire();
}
}
controller
@Autowired
private GuavaRateLimiterService rateLimiterService;
@ResponseBody
@RequestMapping("/ratelimiter")
public Result testRateLimiter(){
if(rateLimiterService.tryAcquire()){
return ResultUtil.success1(1001,"成功获取许可");
}
return ResultUtil.success1(1002,"未获取到许可");
}
- 自定义注解使用
自定义注解
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
String value() default "";
/**
* 每秒放入桶中的令牌数,默认最大即不限流
* @return
*/
double perSecond() default Double.MAX_VALUE;
/**
* 获取令牌的等待时间 默认1
* @return
*/
int timeOut() default 1;
/**
* 超时时间单位 默认:秒
* @return
*/
TimeUnit timeOutUnit() default TimeUnit.SECONDS;
}
切面类
@Aspect
@Component
public class RateLimitAspect {
@Autowired
private HttpServletResponse response;
//存储每个api接口的RateLimiter
public static Map<String, RateLimiter> rateMap = new ConcurrentHashMap<String, RateLimiter>(20);
private static String err = "{\"message\":\"接口调用并发限制\",\"code\":\"203\",\"exception\":{\"message\":\"接口调用并发限制\"}}";
/**
* 定义切入点
* 两种方式可用:
* 1.通过指定包或者指定类切入
*
* @Pointcut("execution(public * com.kunchi.wdt.adapter.spi.*.*(..))")
* 2.通过指定注解切入
* @Pointcut("@annotation(com.kunchi.wdt.adapter.annotation.RateLimit)")
*/
@Pointcut("@annotation(com.kunchi.wdt.elephant.annotation.RateLimit)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object obj = null;
String className = joinPoint.getTarget().getClass().getSimpleName();
// 获取目标方法
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method targetMethod = methodSignature.getMethod();
String methodName = targetMethod.getName();
//根据每个类,方法名字获取rateLimiter
RateLimiter rateLimiter = rateMap.get(className+"."+methodName);
if (rateLimiter == null) {
rateLimiter = RateLimiter.create(Double.MAX_VALUE);
rateMap.put(className+"."+methodName, rateLimiter);
}
if (targetMethod.isAnnotationPresent(RateLimit.class)) {
// 获取目标方法的@RateLimit注解
RateLimit rateLimit = targetMethod.getAnnotation(RateLimit.class);
rateLimiter.setRate(rateLimit.perSecond());
if (!rateLimiter.tryAcquire(rateLimit.timeOut(), rateLimit.timeOutUnit())) {
output(response, err);
} else {
obj = joinPoint.proceed();
}
}
return obj;
}
/**
* 错误返回http处理
*
* @param response
* @param msg
* @throws IOException
*/
public void output(HttpServletResponse response, String msg) throws IOException {
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
outputStream.write(msg.getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
} finally {
outputStream.flush();
outputStream.close();
}
}
}
/**
-
类描述:RateLimit限流测试(基于 注解+切面 方式)
*/
@Controller
public class TestController {@ResponseBody
@RateLimit(perSecond = 3) //可以非常方便的通过这个注解来实现限流 每秒并发为 3
@RequestMapping("/test")
public String test(){
return ResultUtil.success1(1001, “success”).toString();
}