导入依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency>
自定义注解
import java.lang.annotation.*;
//表示该注解被用于方法上
@Target(ElementType.METHOD)
//表示该注解保留到运行时
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
/**
* 该注解实际作用,限流
* rate 代表每秒钟多少请求
*
* @return
*/
int rate() default 100;
/**
* 平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)
* 平滑突发限流,允许突发流量,后面请求速率控制会趋于平稳
* 平滑预热限流,开始先于小于最大速率执行,慢慢趋于平稳,接近最大速率
*
* @return
*/
String mode() default RateLimitMode.SMOOTH_BURSTY;
}
自定义注解的切面处理
import com.google.common.util.concurrent.RateLimiter;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
@Order(100)
public class RateLimitAspect {
/**
* Guava RateLimiter提供的令牌桶算法可用于平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。
* 设计思路----------------------------
* 进入切面后,先进入环绕通知,环绕通知里取出注解限定的速度,并以该速度生产令牌放入桶中,(令牌生产速度为 每秒多少个),在ConcurrentHashMap中以类名+方法名作为key,以RateLimit作为value
* * before执行时先去桶中获取令牌,获取不到阻塞一段时间。
*/
private static Logger LOGGER = LoggerFactory.getLogger(RateLimitAspect.class);
public static final int PERMITS_PER_SECOND = 100;
private static ConcurrentHashMap<String, RateLimiter> RateLimiterMap = new ConcurrentHashMap<>();
/**
* 切入点
*/
@Pointcut("@annotation(com.example.aop.RateLimit)")
public void rateLimitCutPoint() {
LOGGER.info("编入切入点");
}
@Before("rateLimitCutPoint()")
public void rateLimitBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName();
RateLimiter rateLimiter = RateLimiterMap.getOrDefault(methodName, RateLimiter.create(PERMITS_PER_SECOND));
//获取令牌并计算等待时间 此处可以根据waitTime超过阈值就抛出异常(配合全局异常捕获封装指定返回值,提示接口已经限流了)
double waitTime = rateLimiter.acquire();
LOGGER.info("acquire spend {} seconds", waitTime);
}
/**
* 环绕通知必须有返回值, 返回值即为目标方法的返回值
*
* @param proceedingJoinPoint
* @throws Throwable
*/
@Around("rateLimitCutPoint()")
public Object rateLimitAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
try {
Signature signature = proceedingJoinPoint.getSignature();
//返回进行方法的全名称
String methodName = proceedingJoinPoint.getTarget().getClass().getName() + "." + signature.getName();
RateLimit rateLimitAnnotation = ((MethodSignature) signature).getMethod().getDeclaredAnnotation(RateLimit.class);
//注解上的速率
int rate = rateLimitAnnotation.rate();
String mode = rateLimitAnnotation.mode();
putRate(methodName, rate, mode);
LOGGER.info("限流方法名{},执行速率{}", methodName, RateLimiterMap.get(methodName).getRate());
} catch (Exception e) {
LOGGER.error("限流切面出现异常,异常提示{}", e.getMessage());
}
return proceedingJoinPoint.proceed();
}
private void putRate(String methodName, int rate, String mode) {
if (Objects.isNull(RateLimiterMap.get(methodName))) {
if (mode.equals(RateLimitMode.SMOOTH_BURSTY)) {
//SmoothBursty模式下,未使用RateLimit最多保存1秒的令牌,即maxBurstSeconds为1秒
RateLimiterMap.putIfAbsent(methodName, RateLimiter.create(rate));
} else {
//SmoothWarmingUp模式
// warmupPeriod表示从冷启动速率过渡到平均速率所需要的时间间隔,设置为1分钟
RateLimiterMap.putIfAbsent(methodName, RateLimiter.create(rate, 1, TimeUnit.MINUTES));
}
}
}
}
使用到的常量
public class RateLimitMode {
/**
* 平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)
* 平滑突发限流,允许突发流量,后面请求速率控制会趋于平稳
* 平滑预热限流,开始先于小于最大速率执行,慢慢趋于平稳,接近最大速率
*
* @return
*/
public static final String SMOOTH_BURSTY = "smooth-bursty";
public static final String SMOOTH_WARMING_UP = "smooth-warming-up";
}
测试接口
@GetMapping("limit")
@RateLimit(rate = 50)
public String test2() {
return "访问成功";
}
Jmeter测试结果
- 当 @RateLimit(rate = 50),接口吞吐量(throughput)=47
- 当 @RateLimit(rate = 10),接口吞吐量(throughput)=9.7
- 当 @RateLimit(rate = 5),接口吞吐量(throughput)=5
限流成功