☰
Redis+Lua分布式限流的详解
2026/10/28 1:42:14 网站建设 项目流程

基本流程

Controller / Service 方法上标 @RateLimit(...) ↓ 请求进来 AOP 切面拦截(@Around) ↓ 构建限流 key 把 key + 阈值 + 窗口 传给 Lua 脚本 ↓ Redis 原子执行计数 ↓ 返回 1(放行)/ 0(拒绝) 切面:0 → 抛异常挡下;1 → 放行继续执行业务
  • 先是@RateLimit注解(自定义的注解)写在要限流的接口上,注解传参
//案例 1:弹幕限流( DanmakuServiceImpl )——每个用户每 1 秒最多发 1 条弹幕:@RateLimit(prefix="danmaku_send",// 业务前缀maxCount=1,// 窗口内最多 1 次windowSeconds=1,// 窗口 1 秒type=RateLimit.LimitType.CUSTOM,// 自定义 keykeyExpression="#dto.participantId")// 用参与者的 ID 作为 keypublicvoidsend(DanmakuDTOdto){...}
  • 然后用切面RateLimitAspect来拦截被注解的方法,执行限流判断
@Around("@annotation(rateLimit)")// 拦截所有标了 @RateLimit 的方法publicObjectaround(ProceedingJoinPointjoinPoint,RateLimitrateLimit){StringlimitKey=buildLimitKey(joinPoint,rateLimit);// ① 拼 keyLongresult=stringRedisTemplate.execute(rateLimitScript,Collections.singletonList(limitKey),// KEYS[1]String.valueOf(rateLimit.maxCount()),// ARGV[1] 最大次数String.valueOf(rateLimit.windowSeconds()));// ARGV[2] 窗口秒数if(result==null||result==0){// ② 被拒了thrownewBusinessException(429,rateLimit.message());// ③ 抛429异常}returnjoinPoint.proceed();// ④ 放行,继续执行原方法}
  • 执行的限流判断写在Lua脚本里面,让后被Redis执行

固定窗口算法

  • 把时间片切成一段段固定长度的“窗口”,每个窗口内请求次数超过上限就拒绝,窗口翻篇后就清零再来

Lua脚本的拆解

-- KEYS[1] = 限流 key,比如 rate_limit:danmaku:192.168.1.1-- ARGV[1] = 窗口内最大请求次数(比如 3)-- ARGV[2] = 时间窗口秒数(比如 60)localkey=KEYS[1]-- ① 取出 keylocalmaxCount=tonumber(ARGV[1])-- ② 最大次数localwindowSeconds=tonumber(ARGV[2])-- ③ 窗口秒数localcurrent=redis.call('INCR',key)-- ④ 关键:原子+1ifcurrent==1then-- ⑤ 第一次请求?redis.call('EXPIRE',key,windowSeconds)-- ⑥ 才开始计时窗口endifcurrent>maxCountthen-- ⑦ 超了?return0-- ⑧ 拒绝endreturn1-- ⑨ 放行
  • 参数从哪来:Lua 脚本本身不带数据,是 Java 这边把 key、阈值、窗口传进来的(切面里 execute 传的),用 KEYS[1] / ARGV[1] / ARGV[2] 接收

Redis里面唯一的key怎么拼的

StringbaseKey="rate_limit:"+rateLimit.prefix();// IP → rate_limit:scan_login:ip:192.168.1.1// USER → rate_limit:xxx:user:123// CUSTOM → rate_limit:danmaku_send:custom:456
  • 比如扫码登录用 prefix=“scan_login” + type=IP ,拼出来的 key 是 rate_limit:scan_login:ip:192.168.1.1 ——每个 IP 一个独立的计数。

总揽

用户疯狂点"发弹幕" ↓ DanmakuServiceImpl.send() 标着 @RateLimit ↓ RateLimitAspect 拦截(AOP @Around) ↓ 拼 key → rate_limit:danmaku_send:custom:456 ↓ StringRedisTemplate.execute(Lua脚本, key, 1, 1) ↓ Redis 原子执行: INCR rate_limit:danmaku_send:custom:456 → 1(第1次) 是第一次 → EXPIRE 1秒 1 > 1? 否 → 返回 1(放行)✓ ······ 1秒内又来一次: INCR → 2 → 2 > 1? 是 → 返回 0(拒绝)✗ ↓ 切面收到 0 → throw BusinessException(429, "请求过于频繁...") ↓ 全局异常处理 → 前端收到 {code:429, message:"..."}

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询