限流必须在鉴权后、业务前统一拦截,如HandlerInterceptor.preHandle()中调用tryAcquire();若仅放在Controller层,则易被内网RPC、伪造token等绕过,失去防穿透作用。

限流必须在鉴权后、业务前统一拦截
限流组件如果只在 Controller 层调用 tryAcquire(),等于把守门人放在了门口大厅,而攻击者能从消防通道、员工电梯甚至屋顶直接跳进数据库机房。真实穿透常绕过 HTTP 入口:内网 RPC 调用、未鉴权的健康检查端点、伪造 token 的直连请求——这些都不会触发 Controller 里的限流逻辑。
正确做法是把限流嵌入 Spring 的 HandlerInterceptor.preHandle() 或自定义注解切面,在 JWT 解析完成、用户身份确认之后、任何 Service 方法执行之前就完成校验。这样无论请求来自网关、Feign 还是内部 Dubbo,只要走 Spring MVC 生命周期,就逃不过这一关。
- 确保
preHandle()返回false时直接写响应并 return,不放行到后续链路 - 避免在多个 Service 方法里分散调用
tryAcquire(),易漏分支、难审计 - 若用 Redisson,
RRateLimiter必须用RateType.PER_CLIENT,name 参数要拼入真实用户标识(如"user_rate:" + userId),不能写死
Redis 限流不是防穿透,而是防雪崩的兜底闸门
很多人误以为加了布隆过滤器或空值缓存,就不用限流了。但缓存击穿(单个热点 key 过期)和雪崩(大量 key 同时过期)本质是“合法请求暴增”,布隆过滤器对它们完全无效——它只在 bloomFilter.mightContain(key) 返回 false 时起作用;一旦返回 true(哪怕数据库里真没这数据),流程仍会走缓存 → 查库,照样可能打穿。
所以限流在这里的角色不是替代布隆过滤器,而是最后一道物理闸门:当 1000 个用户同时查刚过期的 product:1001,Redis 计数器发现已超阈值(比如 10 秒内最多 5 次),直接返回 429 Too Many Requests,连数据库连接都不建。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 固定窗口算法(
INCR+EXPIRE)够用,但要注意临界突刺问题;生产建议用滑动窗口 Lua 脚本(基于 ZSET),精度更高 - Key 设计必须带业务维度,例如
sliding:uid:123:product:1001,避免不同用户/商品互相干扰 - 别依赖 Redis 单点:加连接健康检查,Redis 不可用时自动降级为本地
Caffeine限流,或启用熔断开关
线程池只管执行,不管放行决策
常见反模式是让线程池参与“等窗口释放”:请求进来先塞进队列,等 Redis 计数器刷新再处理。这违背限流本意——限流目标是快速拒绝,不是排队压测 JVM。
正确分工是:Redis 做原子化判断(允许/拒绝),仅“允许”的请求才提交给专用线程池执行业务逻辑。这样既防止 Redis 故障拖垮应用,又避免慢 SQL 或第三方调用阻塞整个 Tomcat 线程池。
- 为限流后业务定义独立线程池,如
coreApiThreadPool,核心线程数按接口平均 RT 和目标 QPS 反推(例:200ms × 1000 QPS ≈ 200 线程) - 队列必须有界(如
ArrayBlockingQueue(100)),拒绝策略设为CallerRunsPolicy,让 Web 容器线程自己执行,自然限速 - 所有日志打上线程名前缀(如
"core-api-pool-3"),出问题时一眼定位是限流漏了,还是线程池堵了
令牌桶参数设置不当比不限流更危险
Redisson 的 trySetRate(RateType, rate, rateInterval, RateIntervalUnit) 四个参数里,rate 实际是桶容量(不是速率),rateInterval 才是填充周期。设成 rate=100, rateInterval=1, SECONDS,意思是“每秒往桶里加 1 个令牌,桶最大装 100 个”——这会导致突发流量瞬间耗光 100 令牌,之后整整 100 秒都拒绝请求,用户点击十次全失败。
真正合理的配置应匹配业务容忍度:登录接口建议 rate=5(桶大小 5)、rateInterval=60(每分钟补 1 个),既能防暴力撞库,又不误伤正常用户;而消息推送可设为 rate=1000, rateInterval=1。
- 永远不要用
RateType.OVERALL防击穿——它只控总量,无法阻止单个恶意用户刷爆一个 key - 如果业务用 JWT,务必在解析后校验 signature,否则攻击者篡改
userId字段就能绕过 PER_CLIENT 限流 - 测试时用
redis-cli --eval直接跑 Lua 脚本验证逻辑,别只信单元测试——分布式场景下原子性只能靠 Redis 保证

















