Redisson分布式限流从入门到调优:一次秒杀事故逼出来的实战笔记
Redisson分布式限流从入门到调优一次秒杀事故逼出来的实战笔记【免费下载链接】redissonRedisson: Valkey Redis Java Client and Real-Time Data Platform. Sync/Async/RxJava/Reactive API. Over 50 Valkey and Redis based Java objects and services: Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Spring, Tomcat, Scheduler, JCache API, Hibernate, RPC, local cache..项目地址: https://gitcode.com/GitHub_Trending/re/redisson如果你维护过电商或开放平台的后端大概率经历过这样的凌晨秒杀活动一开闸监控面板瞬间飘红数据库连接池被打满用户一边骂转圈圈一边刷新抢购页。这时候你才意识到限流不是锦上添花而是保命符。这篇笔记以一次真实的秒杀抢购事故为起点用实战派的视角带你从选型、落地、看原理到调优一步步把Redisson 分布式限流装进自己的项目里。项目源码和完整文档都可以在仓库中对应位置找到核心实现类在redisson/src/main/java/org/redisson/RedissonRateLimiter.java配套的接口定义在redisson/src/main/java/org/redisson/api/RRateLimiter.java中文能力说明可参考docs/data-and-services/objects.md的 RateLimiter 小节。事故复盘没有限流的接口是怎么被流量教育的那场事故的路径非常典型活动运营在 20:00 放出一批限量商品提前涌入的用户在整点一起点按钮瞬时 QPS 冲到了平时峰值的 30 倍。网关层没有任何限制请求像洪水一样灌进商品服务再一路打到库存服务和数据库。等我们把服务重启、流量降下来之后复盘时列了三宗罪入口没有闸门所有流量直接打到业务层中间没有任何泄洪机制单机限流不顶用之前也想过限流但用的是应用内计数器每个实例各限各的加起来总量完全失控失败没有兜底被拒绝的请求直接 500用户只能反复重试流量越重试越大形成恶性循环。结论只有一句话需要一个跨实例、全局生效的限流器而且它最好和业务代码解耦接入成本足够低。于是我们把目光投向了 Redisson。关键点限流解决的不是性能变好而是在流量超载时系统依然可控。先想清楚这一点后面的选型才不会跑偏。方案选型Guava、Redis 单命令和 Redisson到底差在哪市面上常见的限流方案有三种很多新手上来就选结果踩坑。我们逐一对比过方案作用域原子性运维成本适合场景Guava RateLimiter单进程本地低单机小应用Redis 计数命令INCREXPIRE全局单命令原子中简单固定窗口Redisson RRateLimiter全局Lua 脚本原子低分布式高并发为什么最终选了 Redisson三个理由足够有说服力开箱即用零脚本Redis 原生命令做限流要自己设计 key 结构、写 Lua 脚本、处理窗口边界而 Redisson 把这一切封装成了RRateLimiter一个对象拿过来就能用令牌桶语义完整支持突发量、批量获取许可、等待超时语义和 Guava 几乎一致学习成本极低API 形态全同步、异步、Reactive、RxJava3 四种接口随你挑在 WebFlux 项目里也能无缝嵌入参考redisson/src/main/java/org/redisson/api/RRateLimiterAsync.java与RRateLimiterReactive.java。小结选型的本质是作用域 × 原子性 × 成本三维权衡。Redis 是天然的多实例共享状态Redisson 在这个底座上补全了原子操作和友好 API于是成了分布式限流的最优解。三步把 Redisson 限流接入 Spring Boot以用户维度限流为例思路清楚了就动手。我们的第一步不是给整个秒杀接口做全局限流而是先做用户维度限流——每个用户每秒最多点 5 次抢购按钮防止脚本和机器人大批量刷接口。这一步防的是个体滥用。第 1 步引入依赖并创建客户端。仓库中redisson-spring/redisson-spring-boot-starter模块提供了 Spring Boot 自动装配配置好RedissonClient后直接注入即可。第 2 步写一个限流工具类。以 userId 为 key 维度隔离限流器每个用户一把独立的令牌桶Service public class UserRateLimitService { private final RedissonClient redisson; public UserRateLimitService(RedissonClient redisson) { this.redisson redisson; } // 每个用户每秒最多 5 次操作 public boolean tryPass(String biz, String userId) { RRateLimiter limiter redisson.getRateLimiter(rl: biz :user: userId); // 只在该限流器从未配置过时生效重复调用返回 false天然防覆盖 limiter.trySetRate(RateType.PER_CLIENT, 5, Duration.ofSeconds(1)); // 拿不到许可立刻返回不阻塞业务线程 return limiter.tryAcquire(); } }第 3 步在业务入口处调用。抢购按钮的 Controller 里加一行判断拿不到许可就返回操作太频繁PostMapping(/seckill/click) public ResultVoid click(RequestParam Long userId, RequestParam Long itemId) { if (!userRateLimitService.tryPass(seckill, String.valueOf(userId))) { return Result.fail(429, 手速太快啦请稍后再试); } // 继续走真正的下单流程... return seckillService.submit(userId, itemId); }到这里用户维度限流就上线了。你会发现整个过程没有写一行 Lua没有设计 key 结构只动了业务代码——这正是 Redisson 的价值所在。小结接入三步走——注入客户端、封装限流器、业务入口拦截。先做用户维度成本低、见效快也能为后面的全局限流积累经验。兜底全局限流拿到许可再放行拿不到就优雅降级用户维度防住了个体,还要防住群体。整点放量时即使每个用户只点 5 次上万人同时来总量依然惊人。所以我们要在秒杀接口外面再加一道全局限流整个集群每秒最多放行 8000 个请求进业务层超出部分直接走降级页不再让请求穿透到数据库。这次的代码和用户维度略有不同重点看两个细节OVERALL表示全局限量trySetRate的第四个参数keepAliveTime让限流器在活动结束后自动过期避免 key 永远留在 Redis 里public class SeckillGlobalLimiter { private static final long GLOBAL_RATE 8000; public boolean tryEnter(String itemId) { RRateLimiter limiter redisson.getRateLimiter(rl:seckill:item: itemId); // 每秒 8000 个许可活动结束后 2 小时无访问自动清理 limiter.trySetRate(RateType.OVERALL, GLOBAL_RATE, Duration.ofSeconds(1), Duration.ofHours(2)); return limiter.tryAcquire(); } }在网关过滤器里配合使用拿不到许可就返回 429 并落到降级页而不是 500 让用户反复重试if (seckillGlobalLimiter.tryEnter(itemId)) { return chain.filter(exchange); // 放行进入业务链路 } exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS); return exchange.getResponse().setComplete(); // 直接返回不穿透如果项目用的是 WebFluxRedisson 还提供了响应式版本的限流器Mono链路上直接拿许可不阻塞事件循环RRateLimiterReactive limiter redissonReactive.getRateLimiter(rl:api: apiName); MonoBoolean ok limiter.trySetRate(RateType.OVERALL, 1000, Duration.ofSeconds(1)) .then(limiter.tryAcquire());小结全局限流是总量闸门配合网关在入口拦截加上降级页兜底才能真正把流量挡在业务层之外。拒绝要温柔——返回明确的 429 而不是让用户看到 500。原理没那么玄RedissonRateLimiter 的令牌桶是怎么转起来的用熟了之后我们都会好奇它是怎么保证多实例下计数不打架的翻开RedissonRateLimiter.java的源码逻辑其实很清晰。核心是令牌桶模型每个限流器在 Redis 里维护一组 keypermits记录许可池value记录时间戳和已用许可数。每次tryAcquire都会执行一段 Lua 脚本完成三件事根据当前时间与上次补充时间的差值计算出该补多少令牌进池子补满为止判断池子里剩余令牌是否够本次请求消耗够就扣减并返回 1不够就返回 0。整个过程在 Redis 服务端单线程执行一段 Lua 脚本天然原子不存在并发写冲突。这也是为什么它在多实例、多线程下依然准确——你不需要在应用层加任何锁。另外有两个容易被忽略的实用 APIavailablePermits()查看当前剩余许可可用于监控和告警getConfig()返回RateLimiterConfig包含当前的RateType、速率和时间间隔方便把配置暴露到管理接口里。小结RedissonRateLimiter 令牌桶语义 Lua 原子脚本 多 API 形态。理解补令牌—扣令牌两步循环就抓住了它全部原理剩下的都是包装。限流阈值怎么定才不误伤两个口诀和一张对照表原理懂了新的灵魂拷问来了阈值定多少定高了等于没限定低了误伤正常用户。我们沉淀了两条经验口诀一从下往上算。不要拍脑袋定 QPS而是找到系统最脆弱的环节——通常是数据库——测出它的安全吞吐比如 3000 TPS那么接口阈值就设 3000 × 80% 2400留出 20% 余量应对抖动。口诀二区分粒度再设值。用户维度、接口维度、全局维度是三层不同的限流阈值逻辑完全不同限流维度典型阈值防的是什么用户维度PER_CLIENT每用户每秒 5 次脚本刷单、单人多开接口维度每秒 8000 次单接口突发流量全局维度OVERALL按压测 80% 取值整体洪峰打穿链路这里顺便回答一个高频疑问全局限流 vs 客户端限流怎么选一句话——OVERALL对整体总量负责适合秒杀、抢购这种总盘子有限的场景PER_CLIENT对单个 Redisson 实例的限额负责适合每个节点各管一摊的场景。绝大多数生产系统两者组合使用。小结阈值是算出来的不是拍出来的——从最脆弱环节反推再叠加维度分层。先有数据再谈限流。上线后必须做的三件事监控、告警与五个常见坑限流上线只是开始真正的考验在运行期。我们踩过的坑都值得你提前知道坑一trySetRate 只在首次生效。很多人拿它当改配置用改完发现没效果——它返回false表示已存在。要动态调阈值用setRate或updateRate后者还能通过keepState决定是否保留当前计数状态。坑二key 无限增长。用户维度限流会为每个用户建 key日活千万就会产生千万个 key。一定要配合keepAliveTime让限流器自动过期否则 Redis 内存会被拖垮。坑三阻塞 API 慎用于高并发。acquire()会阻塞等待许可线程池不足时反而拖垮应用。网关场景优先用tryAcquire()快速失败。坑四旧版 API 已标记废弃。trySetRate(RateType, long, long, RateIntervalUnit)系列在源码中已标注Deprecated新代码请改用Duration版本见RRateLimiter.java中的注释说明。坑五忘记观察拒绝量。限流后不看 429 数量等于盲飞。建议定时任务或 Actuator 端点定期拉取availablePermits()和拒绝计数接入 Prometheus 告警拒绝率飙升说明阈值过低或流量异常需要人工介入。小结监控是限流的另一半。没有告警的限流配置就像没有仪表盘的驾驶——你只知道自己限了不知道限得对不对。收尾一张可以直接抄的调优清单最后把整个实战过程压缩成一张清单下次做新项目直接照着勾先压测找瓶颈阈值 瓶颈安全值 × 80%用户维度限流防个体滥用接口/全局维度防总量洪峰入口网关用tryAcquire()快速失败 429 降级页所有限流 key 配置keepAliveTime防内存泄漏动态调阈值用setRate/updateRate别用trySetRate接监控剩余许可、拒绝率、429 数量三项指标必看限流不是银弹但它是分布式系统最便宜的一层保险。把这套流程跑通一次你会发现自己面对流量冲击时心态从祈祷别挂变成了来多少都接得住。去动手试试吧把第一个限流器跑起来你的系统就多了一层护盾。【免费下载链接】redissonRedisson: Valkey Redis Java Client and Real-Time Data Platform. Sync/Async/RxJava/Reactive API. Over 50 Valkey and Redis based Java objects and services: Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Spring, Tomcat, Scheduler, JCache API, Hibernate, RPC, local cache..项目地址: https://gitcode.com/GitHub_Trending/re/redisson创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考