导入依赖

  1. <dependency>
  2. <groupId>com.google.guava</groupId>
  3. <artifactId>guava</artifactId>
  4. <version>30.1.1-jre</version>
  5. </dependency>

自定义注解

  1. import java.lang.annotation.*;
  2. //表示该注解被用于方法上
  3. @Target(ElementType.METHOD)
  4. //表示该注解保留到运行时
  5. @Retention(RetentionPolicy.RUNTIME)
  6. @Documented
  7. public @interface RateLimit {
  8. /**
  9. * 该注解实际作用,限流
  10. * rate 代表每秒钟多少请求
  11. *
  12. * @return
  13. */
  14. int rate() default 100;
  15. /**
  16. * 平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)
  17. * 平滑突发限流,允许突发流量,后面请求速率控制会趋于平稳
  18. * 平滑预热限流,开始先于小于最大速率执行,慢慢趋于平稳,接近最大速率
  19. *
  20. * @return
  21. */
  22. String mode() default RateLimitMode.SMOOTH_BURSTY;
  23. }

自定义注解的切面处理

  1. import com.google.common.util.concurrent.RateLimiter;
  2. import org.aspectj.lang.JoinPoint;
  3. import org.aspectj.lang.ProceedingJoinPoint;
  4. import org.aspectj.lang.Signature;
  5. import org.aspectj.lang.annotation.Around;
  6. import org.aspectj.lang.annotation.Aspect;
  7. import org.aspectj.lang.annotation.Before;
  8. import org.aspectj.lang.annotation.Pointcut;
  9. import org.aspectj.lang.reflect.MethodSignature;
  10. import org.slf4j.Logger;
  11. import org.slf4j.LoggerFactory;
  12. import org.springframework.core.annotation.Order;
  13. import org.springframework.stereotype.Component;
  14. import java.util.Objects;
  15. import java.util.concurrent.ConcurrentHashMap;
  16. import java.util.concurrent.TimeUnit;
  17. @Aspect
  18. @Component
  19. @Order(100)
  20. public class RateLimitAspect {
  21. /**
  22. * Guava RateLimiter提供的令牌桶算法可用于平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。
  23. * 设计思路----------------------------
  24. * 进入切面后,先进入环绕通知,环绕通知里取出注解限定的速度,并以该速度生产令牌放入桶中,(令牌生产速度为 每秒多少个),在ConcurrentHashMap中以类名+方法名作为key,以RateLimit作为value
  25. * * before执行时先去桶中获取令牌,获取不到阻塞一段时间。
  26. */
  27. private static Logger LOGGER = LoggerFactory.getLogger(RateLimitAspect.class);
  28. public static final int PERMITS_PER_SECOND = 100;
  29. private static ConcurrentHashMap<String, RateLimiter> RateLimiterMap = new ConcurrentHashMap<>();
  30. /**
  31. * 切入点
  32. */
  33. @Pointcut("@annotation(com.example.aop.RateLimit)")
  34. public void rateLimitCutPoint() {
  35. LOGGER.info("编入切入点");
  36. }
  37. @Before("rateLimitCutPoint()")
  38. public void rateLimitBefore(JoinPoint joinPoint) {
  39. String methodName = joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName();
  40. RateLimiter rateLimiter = RateLimiterMap.getOrDefault(methodName, RateLimiter.create(PERMITS_PER_SECOND));
  41. //获取令牌并计算等待时间 此处可以根据waitTime超过阈值就抛出异常(配合全局异常捕获封装指定返回值,提示接口已经限流了)
  42. double waitTime = rateLimiter.acquire();
  43. LOGGER.info("acquire spend {} seconds", waitTime);
  44. }
  45. /**
  46. * 环绕通知必须有返回值, 返回值即为目标方法的返回值
  47. *
  48. * @param proceedingJoinPoint
  49. * @throws Throwable
  50. */
  51. @Around("rateLimitCutPoint()")
  52. public Object rateLimitAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
  53. try {
  54. Signature signature = proceedingJoinPoint.getSignature();
  55. //返回进行方法的全名称
  56. String methodName = proceedingJoinPoint.getTarget().getClass().getName() + "." + signature.getName();
  57. RateLimit rateLimitAnnotation = ((MethodSignature) signature).getMethod().getDeclaredAnnotation(RateLimit.class);
  58. //注解上的速率
  59. int rate = rateLimitAnnotation.rate();
  60. String mode = rateLimitAnnotation.mode();
  61. putRate(methodName, rate, mode);
  62. LOGGER.info("限流方法名{},执行速率{}", methodName, RateLimiterMap.get(methodName).getRate());
  63. } catch (Exception e) {
  64. LOGGER.error("限流切面出现异常,异常提示{}", e.getMessage());
  65. }
  66. return proceedingJoinPoint.proceed();
  67. }
  68. private void putRate(String methodName, int rate, String mode) {
  69. if (Objects.isNull(RateLimiterMap.get(methodName))) {
  70. if (mode.equals(RateLimitMode.SMOOTH_BURSTY)) {
  71. //SmoothBursty模式下,未使用RateLimit最多保存1秒的令牌,即maxBurstSeconds为1秒
  72. RateLimiterMap.putIfAbsent(methodName, RateLimiter.create(rate));
  73. } else {
  74. //SmoothWarmingUp模式
  75. // warmupPeriod表示从冷启动速率过渡到平均速率所需要的时间间隔,设置为1分钟
  76. RateLimiterMap.putIfAbsent(methodName, RateLimiter.create(rate, 1, TimeUnit.MINUTES));
  77. }
  78. }
  79. }
  80. }

使用到的常量

  1. public class RateLimitMode {
  2. /**
  3. * 平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)
  4. * 平滑突发限流,允许突发流量,后面请求速率控制会趋于平稳
  5. * 平滑预热限流,开始先于小于最大速率执行,慢慢趋于平稳,接近最大速率
  6. *
  7. * @return
  8. */
  9. public static final String SMOOTH_BURSTY = "smooth-bursty";
  10. public static final String SMOOTH_WARMING_UP = "smooth-warming-up";
  11. }

测试接口

  1. @GetMapping("limit")
  2. @RateLimit(rate = 50)
  3. public String test2() {
  4. return "访问成功";
  5. }

Jmeter测试结果

  1. 当 @RateLimit(rate = 50),接口吞吐量(throughput)=47
  2. 当 @RateLimit(rate = 10),接口吞吐量(throughput)=9.7
  3. 当 @RateLimit(rate = 5),接口吞吐量(throughput)=5

限流成功