一、前言
在高并发系统中,为了防止接口被恶意频繁访问、刷接口或突发流量导致系统崩溃,我们需要 限流(Rate Limiting)。本文将介绍如何通过 Redis + AOP 在 Spring Boot 项目中实现轻量级、可复用的接口限流功能。
相比使用 Guava 或令牌桶算法的单机实现,Redis 版本支持 分布式限流,非常适合多实例部署场景。
二、Maven 依赖
1 2 3 4 5
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
|
三、自定义注解
通过注解来灵活定义接口限流的规则,例如访问时间窗口与次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CurrentLimiting {
String key() default "apiVisits:";
int time() default 5;
int count() default 10; }
|
四、AOP 切面实现
利用 AOP 在接口调用前拦截请求,并基于 Redis 的 ZSet(有序集合) 实现滑动时间窗口限流。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| @Slf4j @Aspect @Component @RequiredArgsConstructor public class CurrentLimitingAspect {
private final RedisTemplate<String, Object> redisTemplate;
@Before("@annotation(currentLimiting)") public void doBefore(JoinPoint point, CurrentLimiting currentLimiting) throws Throwable { int time = currentLimiting.time(); int count = currentLimiting.count();
String key = getCurrentLimitingKey(currentLimiting.key(), point);
ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet(); long currentTime = System.currentTimeMillis();
zSetOps.add(key, currentTime, currentTime); redisTemplate.expire(key, time, TimeUnit.SECONDS);
zSetOps.removeRangeByScore(key, 0, currentTime - time * 1000);
Long currentCount = zSetOps.zCard(key);
if (currentCount != null && currentCount > count) { log.warn("接口限流触发 -> key:{}, count:{}, currentCount:{}", key, count, currentCount); throw new RuntimeException("访问过于频繁,请稍后再试!"); } }
private String getCurrentLimitingKey(String prefixKey, JoinPoint point) { StringBuilder sb = new StringBuilder(prefixKey);
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest();
String ip = request.getRemoteAddr(); MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); Class<?> targetClass = method.getDeclaringClass();
return sb.append(ip) .append(":") .append(targetClass.getName()) .append(":") .append(method.getName()) .toString(); } }
|
五、注解使用示例
在控制器方法上直接使用 @CurrentLimiting 注解即可,灵活控制不同接口的限流策略。
1 2 3 4 5 6 7 8 9 10 11 12
| import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
@RestController public class DemoController {
@GetMapping("/hello") @CurrentLimiting(time = 5, count = 2) public String hello() { return "Hello, World!"; } }
|
运行后,访问 /hello 接口 5 秒内超过 2 次,将触发异常:
六、总结
使用 Redis ZSet 实现滑动窗口算法,统计当前时间段内访问次数。
AOP 拦截 + 自定义注解,使限流逻辑与业务逻辑完全解耦。
可轻松扩展至分布式系统,配合 Lua 脚本可实现原子操作。
作者:NowPion