在高并发场景下,接口的流量控制是保证系统稳定性和提升性能的关键之一。通过实现接口限流,我们可以有效避免系统在访问高峰时发生崩溃。本文将详细介绍如何通过自定义注解和切面编程结合RateLimiter
来实现接口的限流功能,以应对高并发请求。
什么是RateLimiter?
RateLimiter
是Guava提供的一个工具类,用于控制某些资源的访问频率。它通过令牌桶算法来限制并发请求的数量。我们可以通过设置每秒请求数(QPS)来控制接口的访问速率,避免瞬间请求过多导致系统过载。
实现思路
在本例中,我们通过以下几个步骤来实现接口限流:
- 自定义注解:我们定义一个
@Limiter
注解来标记需要进行限流的接口方法。 - 切面编程:利用Spring AOP技术,在执行标记了
@Limiter
注解的方法前后做处理。 - RateLimiter:根据
QPS
和concurrency
参数,创建RateLimiter
实例并在方法执行时控制访问速率。
一、定义Limiter注解
首先,我们定义一个@Limiter
注解,用于标记哪些方法需要进行限流控制。注解中包含两个参数:QPS
(每秒请求数)和concurrency
(并发量)。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Limiter {
double QPS() default Double.MAX_VALUE; // 限流的QPS
int concurrency() default 1; // 允许的并发数
}
@Retention
和@Target
注解指定了这个注解的作用范围和生命周期。QPS
默认值为Double.MAX_VALUE
,意味着不限制请求速率;concurrency
默认值为1,表示每次只能允许1个请求并发执行。
二、RateLimiter切面类实现
接下来,我们创建一个RateLimiterAspect
切面类,利用Spring AOP拦截带有@Limiter
注解的方法。我们根据注解的参数动态创建RateLimiter
实例,并控制方法的执行。
@Aspect
@Component
public class RateLimiterAspect {
private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
private Map<String, RateLimiter> limiterMap = new ConcurrentHashMap<>();
public RateLimiterAspect() {}
@Around("execution(* *(..)) && @annotation(limiter)")
public Object around(ProceedingJoinPoint joinPoint, Limiter limiter) throws Throwable {
// 获取当前方法名并根据QPS创建RateLimiter
RateLimiter rateLimiter = getOrCreateRateLimiter(getMethodName(joinPoint), limiter.QPS());
rateLimiter.acquire(); // 获取一个许可
// 记录执行时间
Stopwatch stopwatch = Stopwatch.createStarted();
Object ret = joinPoint.proceed(joinPoint.getArgs());
double elapsed = stopwatch.elapsed(TimeUnit.MICROSECONDS);
double rate = TimeUnit.SECONDS.toMicros(1L) / elapsed * limiter.concurrency();
// 设置当前方法的请求速率
rateLimiter.setRate(rate);
return ret;
}
private String getMethodName(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
return String.join(".", methodSignature.getDeclaringType().getSimpleName(), methodSignature.getName());
}
private RateLimiter getOrCreateRateLimiter(String method, double permitsPerSecond) {
return limiterMap.computeIfAbsent(method, key -> RateLimiter.create(permitsPerSecond));
}
}
切面解读
@Around
:这个注解表示我们将在方法执行之前和之后进行操作。通过execution(* *(..))
来匹配所有方法,并且使用@annotation(limiter)
来确保只拦截带有@Limiter
注解的方法。RateLimiter
:每次方法执行时,我们根据QPS
值动态创建一个RateLimiter
实例,使用rateLimiter.acquire()
方法来获取许可,保证每秒的请求不会超过设定的QPS。- 执行时间监控:通过
Stopwatch
来计算方法执行的时间,根据执行时间动态调整速率。
三、使用示例
假设我们有一个接口方法,需要限制每秒最多10个请求,并且允许3个并发请求:
@RestController
@RequestMapping("/u/user")
@CrossOrigin
public class UserController {
@GetMapping("/Test")
@Limiter(QPS = 10, concurrency = 3)
public R Test(@RequestParam String username){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(username + " " + "count:" + count++);
return R.ok().message("test");
}
}
在上面的例子中,getUserInfo
方法被@Limiter
注解标记,意味着每秒最多允许10个请求并发执行最多3次。
我们进行测压,检验结果
注解成功生效!!!
测压的方案可以👀我另一篇文章:保姆级jmeter压测计算QPS教程_jmeter测试qps-CSDN博客