Java Spring AOP 实现分布式接口防刷限流的核心是不改业务代码、统一拦截+分布式状态共享,通过自定义SpEL注解、AOP切面拦截及Redis原子操作(推荐滑动窗口Lua脚本)完成限流,并增强可观测性与黑名单机制。

Java Spring AOP 实现分布式系统的接口防刷限流,核心是“不改业务代码、统一拦截 + 分布式状态共享”。单机限流(如 Guava)在多实例下会失效,必须依赖 Redis 这类共享存储来维护全局计数,并用 AOP 将限流逻辑织入目标方法前。整个过程分三步走:定义可配置的限流注解 → 编写 AOP 切面执行判断 → 用 Redis 原子操作完成计数与过期控制。
定义灵活的限流注解
注解要支持按不同维度生成限流 key,以及自定义时间窗口和阈值:
-
key 支持 SpEL 表达式:比如
"#user.id"(按用户)、"#request.remoteAddr"(按 IP)、"'sms:'+ #phone"(按手机号),便于适配登录、短信、支付等场景 -
time 和 count 可配置:例如
@RateLimit(time = 60, count = 5)表示 1 分钟最多 5 次 -
limitType 区分策略:如
IP、USER_ID、INTERFACE,方便切面中组装唯一 key
用 AOP 切面拦截并执行限流逻辑
切面在方法执行前触发,关键在于安全、高效地完成“读-增-判-设过期”四步:
- 通过
ProceedingJoinPoint获取参数,用 SpEL 解析出最终限流 key(如"rate_limit:sms:138****1234") - 调用 Redis 的
INCR原子自增;若返回值为 1,立即执行EXPIRE key time设置过期时间,避免 key 永久残留 - 比较当前计数值是否 ≤ 阈值;超限则抛出自定义异常(如
LimitExceedException),由全局异常处理器统一返回429 Too Many Requests和提示信息 - 异常兜底:Redis 不可用时,记录 warn 日志但放行请求,保障系统可用性(fail-open 策略)
选择合适的限流算法与 Redis 实现
生产环境推荐滑动窗口或令牌桶,避免固定窗口的临界突刺问题:
立即学习“Java免费学习笔记(深入)”;
-
滑动窗口(推荐):用 Redis 的
ZSET存储时间戳,每次请求先清理过期项(ZREMRANGEBYSCORE),再ZCARD计数,最后ZADD新条目。Lua 脚本封装整个流程,保证原子性 - 令牌桶(适合突发容忍):用 Lua 维护一个“剩余令牌数”和“上次填充时间”,按速率匀速补发,请求时扣减;需注意初始化和预热逻辑
- 固定窗口(仅用于简单兜底):直接用字符串 key + INCR + EXPIRE,实现最简,但慎用于高敏感接口
增强可靠性与可观测性
上线后不能只靠“能用”,还要考虑运维友好:
- 限流 key 加前缀(如
rate:prod:)并设置 TTL,防止测试/灰度环境 key 冲突 - 记录被限流的 IP、接口路径、时间戳到日志或监控系统(如 Prometheus + Grafana),便于定位攻击源
- 配合 Redisson 的
RRateLimiter可直接复用成熟分布式限流器,自动处理令牌生成、续期与集群同步 - 关键接口建议叠加黑名单机制:连续 N 次超限后,将 IP 写入
blacklist:xxx并设长过期(如 1 小时),后续请求直接拒绝


















