Skip to content

Fix atomic multi-dimension rate limiting - #39

Merged
Snailclimb merged 1 commit into
Snailclimb:masterfrom
isCharles:ischarles/rate-limit-atomic-dimensions
Aug 13, 2026
Merged

Fix atomic multi-dimension rate limiting#39
Snailclimb merged 1 commit into
Snailclimb:masterfrom
isCharles:ischarles/rate-limit-atomic-dimensions

Conversation

@isCharles

@isCharles isCharles commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Execute all @RateLimit rules for a method in a single Redis Lua call.
  • Split the Lua logic into a check phase and a deduct phase so no dimension is charged unless every rule passes.
  • Keep the failed-rule mapping so fallback/error handling still points at the dimension that rejected the request.

Root cause

The previous aspect loop called the Lua script once per @RateLimit rule. If an earlier rule such as GLOBAL succeeded and a later rule such as IP failed, the rejected request had already consumed the global token.

Validation

  • .\gradlew.bat :app:compileJava :app:compileTestJava
  • .\gradlew.bat :app:test --tests "interview.guide.common.aspect.RateLimitScriptTest"

Note: RateLimitIntegrationTest remains disabled because it requires a live Redis instance, but its multi-rule case now asserts that a later rejected dimension does not deduct earlier dimensions.

之前 RateLimitAspect 会按 @RateLimit 规则逐条循环调用 Lua 脚本。如果前面的规则,例如 GLOBAL,已经扣减成功,但后面的规则,例如 IP,限流失败,那么这次请求虽然最终被拒绝,却已经消耗了全局令牌。

如果有人疯狂刷接口,即使这些请求最终都被 IP 限流拒绝,也可能持续消耗 GLOBAL 令牌。结果是其他正常用户更早遇到全局限流。统计上也有问题。

更改

  • 将同一个方法上的所有 @RateLimit 规则合并到一次 Redis Lua 调用中执行。
  • Lua 脚本拆成两个阶段:先检查所有限流维度是否都有足够令牌,全部通过后再统一扣减。
  • 保留失败规则的映射关系,因此 fallback 或错误处理仍然能准确指向触发限流的维度。

@Snailclimb Snailclimb left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

当前实现存在会影响限流准确性的阻断问题,建议修改后再合并。

  1. [P1] app/src/main/resources/scripts/rate_limit_single.lua:33-51:第一阶段在所有维度通过前就执行 zremrangebyscore。如果当前维度回收到令牌并通过、后续维度返回 -i,回收后的 current_val 不会写回,但过期记录已经消失,令牌会丢失到 value key 的 TTL 到期。建议检查阶段只读取和计算,所有规则通过后再统一删除过期记录、扣减和写回。

  2. [P1] RateLimitIntegrationTest 仍被 @Disabled,而 RateLimitScriptTest 不执行 Lua。单维拒绝现在返回 -1,测试第 80、117 行仍断言旧值 0,启用后会失败。请增加可在 CI 中执行的真实 Redis 回归测试,并覆盖“前一维度有过期令牌、后一维度拒绝”的场景。

隔离 Redis 7 复现结果:第一次调用返回 -2 后,global 的过期记录已被删除但 value 仍为 0;第二次调用因此返回 -1

@Snailclimb
Snailclimb merged commit efa75e3 into Snailclimb:master Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants