实训7:基于AOP+Redis Lua的滑动窗口限流
本次任务的目标是:实现一套方法级的接口限流,支持全局,IP,用户多个维度,防止知识库问答,文件上传等高成本被频繁调用。
技术栈:
Spring Boot4.0
Spring AOP(AspectJ)
Redisson + Redis Lua脚本
一.整体思路
限流分成三部分:注解负责声明规则,切面负责拦截执行,Lua脚本负责计算判断。
注解:贴在接口方法上,写明限多少次,按什么维度限,本身不含逻辑。
切面:拦截所有带注解的方法,取出规则去Redis判断,通过就放行,不通过就拒绝或降级。
Lua脚本:把计数的检查和扣减放在一起原子执行。
二.核心代码实现
1.自定义@RateLimit注解,用@Repeatable支持在一个方法上声明多条规则:
@Target(ElementType.METHOD)
@Rentention(RetentionPolicy.RUNTIME)
@Repeatable(RateLimit.Container.class)
public @interface RateLimit {
enum Dimension { GLOBAL, IP, USER }
Dimension dimension() default Dimension.GLOBAL;
double count(); // 窗口内允许的最大次数
long interval() default 1; // 时间窗口大小
TimeUnit timeUnit() default TimeUnit.SECONDS;
String fallback() default ""; // 降级方法
}
使用时一个方法可以挂多条,全部通过才放行:
@RateLimit(dimension = Dimension.GLOBAL, count = 10)
@RateLimit(dimension = Dimension.IP, count = 5)
public Result<QueryResponse> queryKnowledgeBase(...) { ... }
2.AOP切面拦截带注解的方法,逐条规则校验,任意一条不通过就拒绝
@Around("@annotation(...RateLimit) || @annotation(...RateLimit.Container)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
RateLimit[] rules = method.getAnnotationsByType(RateLimit.class);
for (RateLimit rule : rules) {
String key = generateKey(className, methodName, rule.dimension());
Long ok = executeRateLimitScript(key, ...);
if (ok == null || ok == 0) {
return handleRateLimitExceeded(joinPoint, rule, key);
}
}
return joinPoint.proceed();
}
3.用Redis Lua脚本做计数判断,基于滑动窗口
-- 当前剩余次数(不存在则取最大值)
local current_val = tonumber(redis.call("get", value_key)) or max_tokens
-- 回收已经超过时间窗口的次数
local expired = redis.call("zrangebyscore", permits_key, 0, now_ms - interval)
-- 累加回收次数、删除过期记录、current_val 加回
if current_val < permits then return 0 end
redis.call("zadd", permits_key, now_ms, request_id .. ":" .. permits)
redis.call("set", value_key, current_val - permits)
return 1
三.几个关键点
为什么用Lua脚本:如果先查剩余次数,再扣减,是两步操作,高并发下两个请求可能同时查到“还剩一次”都放行,导致超限。Lua脚本再Redis里是原子执行的,把检查和扣减放在一个脚本就不会有这个问题。
为什么用滑动窗口:固定窗口(整秒清零)在临界点会翻倍,比如上一秒末尾和下一秒开头各来一批,短时间内实际放行可能是阈值的两倍。滑动窗口按时间戳记录每次请求,下次回收过期的,窗口随时间平滑移动,避免这个问题。
Key 用 Hash Tag:限流 Key 形如 ratelimit:{类名:方法名}:ip:1.2.3.4,用大括号把类名方法名包起来,保证同一方法的多个维度 Key 在 Redis 集群下落到同一槽位,否则 Lua 操作多个 Key 会报 CROSSSLOT。
NOSCRIPT 处理:脚本用 sha 调用,Redis 重启后缓存会丢,切面里捕获 NOSCRIPT 异常后重新加载脚本再执行一次。
四.总结
| 步骤 | 实现方式 |
| 声明规则 | @RateLimit注解 |
| 多维度限流 | 可重复注解,GLOBAL / IP / USER |
| 拦截执行 | AOP 切面 |
| 原子计数 | Redis Lua 脚本 |
| 避免临界翻倍 | 滑动窗口(按时间戳记录 + 回收) |
| 集群兼容 | Key 使用 Hash Tag |
整套限流做成了贴注解即生效,对业务代码无侵入的通用组件。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)