在高并发场景下,接口限流是保障系统稳定性的关键手段之一。它可以有效防止接口被恶意或突发流量压垮,确保核心业务的可用性。本文将介绍如何基于 Spring AOP 与 Redis 实现一个灵活、可配置的注解式接口限流组件。
本方案旨在实现以下功能:
本限流方案主要由以下四个核心组件组成:
@RateLimiter
注解:用于标注需要限流的接口方法;RateLimiterKeyResolver
接口:定义限流 Key 的解析策略;RateLimiterAspect
切面类:拦截注解方法并执行业务逻辑前进行限流检查;RateLimiterRedisDAO
:封装 Redis 限流逻辑,底层基于 Redisson 实现。用于对方法进行限流控制,配置项如下:
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiter {
int time() default 1; // 时间窗口,默认1
TimeUnit timeUnit() default TimeUnit.SECONDS; // 时间单位
int count() default 100; // 最大请求次数
String message() default ""; // 限流提示信息
Class<? extends RateLimiterKeyResolver> keyResolver() default DefaultRateLimiterKeyResolver.class; // Key 解析器
String keyArg() default ""; // 附加参数(可选)
}
定义统一接口 RateLimiterKeyResolver
,用于动态生成限流 Key,实现解耦与扩展:
public interface RateLimiterKeyResolver {
String resolver(JoinPoint joinPoint, RateLimiter rateLimiter);
}
public class ClientIpRateLimiterKeyResolver implements RateLimiterKeyResolver {
@Override
public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {
String methodName = joinPoint.getSignature().toShortString();
String argsStr = StrUtil.join(",", joinPoint.getArgs());
String clientIp = ServletUtils.getClientIP();
return SecureUtil.md5(methodName + argsStr + clientIp);
}
}
支持扩展为基于用户、Token、路径等多种策略。
使用 Spring AOP 实现方法拦截逻辑:
@Aspect
@Slf4j
public class RateLimiterAspect {
private final Map<Class<? extends RateLimiterKeyResolver>, RateLimiterKeyResolver> keyResolvers;
private final RateLimiterRedisDAO rateLimiterRedisDAO;
public RateLimiterAspect(List<RateLimiterKeyResolver> keyResolvers, RateLimiterRedisDAO rateLimiterRedisDAO) {
this.keyResolvers = CollectionUtils.convertMap(keyResolvers, RateLimiterKeyResolver::getClass);
this.rateLimiterRedisDAO = rateLimiterRedisDAO;
}
@Before("@annotation(rateLimiter)")
public void beforePointCut(JoinPoint joinPoint, RateLimiter rateLimiter) {
RateLimiterKeyResolver keyResolver = keyResolvers.get(rateLimiter.keyResolver());
Assert.notNull(keyResolver, "未找到对应的 KeyResolver");
String key = keyResolver.resolver(joinPoint, rateLimiter);
boolean allowed = rateLimiterRedisDAO.tryAcquire(key, rateLimiter.count(), rateLimiter.time(), rateLimiter.timeUnit());
if (!allowed) {
log.warn("[限流触发] 方法: {}, 参数: {}", joinPoint.getSignature(), joinPoint.getArgs());
String message = StrUtil.blankToDefault(rateLimiter.message(), GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getMsg());
throw new ServiceException(GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getCode(), message);
}
}
}
使用 Redisson 的 RRateLimiter
提供令牌桶限流功能:
@AllArgsConstructor
public class RateLimiterRedisDAO {
private static final String RATE_LIMITER = "rate_limiter:%s";
private final RedissonClient redissonClient;
public Boolean tryAcquire(String key, int count, int time, TimeUnit timeUnit) {
RRateLimiter rateLimiter = getRRateLimiter(key, count, time, timeUnit);
return rateLimiter.tryAcquire();
}
private RRateLimiter getRRateLimiter(String key, long count, int time, TimeUnit timeUnit) {
String redisKey = String.format(RATE_LIMITER, key);
RRateLimiter rateLimiter = redissonClient.getRateLimiter(redisKey);
long interval = timeUnit.toSeconds(time);
rateLimiter.trySetRate(RateType.OVERALL, count, interval, RateIntervalUnit.SECONDS);
return rateLimiter;
}
}
在需要限流的 Controller 或 Service 方法上添加注解即可:
@RateLimiter(count = 10, time = 1, timeUnit = TimeUnit.MINUTES, keyResolver = ClientIpRateLimiterKeyResolver.class)
public String getUserInfo(String userId) {
// 业务逻辑
return "User Info";
}
本文介绍了一个基于 Spring AOP 与 Redis(Redisson)实现的注解式接口限流方案。该方案具备以下优势:
适用于微服务架构下高并发接口保护场景,推荐在实际开发中推广使用。