RateLimiter.create() 的合理参数需基于服务吞吐能力、下游SLA和平均耗时计算,如接口200ms响应、单机50QPS即抖动,则起点为create(50.0),并结合burstSize、业务隔离及监控调优。

RateLimiter.create() 的参数到底传多少才合理
限流值不是拍脑袋定的,它得和你的服务真实吞吐能力、下游依赖的 SLA、以及请求的平均耗时强相关。比如你接口平均响应 200ms,单机撑住 50 QPS 就开始抖动,那 RateLimiter.create(50.0) 是起点,不是终点。
常见错误是直接按“每秒最多 100 次”设成 RateLimiter.create(100.0),结果压测时线程池打满、DB 连接超时——因为 RateLimiter 只管“放行”,不管“执行完”。它不阻塞线程,但后续资源没跟上,照样雪崩。
- 突发流量下,
create(double permitsPerSecond)默认允许 1 秒内最多攒 1 个额外令牌(即burstSize = 1),想支持短时突增,得用create(double, long, TimeUnit)显式设maxBurstSeconds - 如果下游是 MySQL,且单条写入平均耗时 50ms,理论极限约 20 QPS/连接,按 5 连接算就是 100 QPS —— 这才是你
create()的上限参考值,不是监控看到的峰值 - 测试时别只看成功率,盯住
RateLimiter.getRate()和实际acquire()耗时:如果 acquire 平均卡 800ms,说明桶早空了,当前限流值已失守
acquire() 和 tryAcquire() 到底该选哪个
90% 的场景该用 tryAcquire(),而不是无脑 acquire()。前者不阻塞,返回 boolean;后者会一直等,直到拿到令牌——这在 Web 请求线程里等于主动给自己挂起,线程池吃紧时直接拖垮整个实例。
典型误用:在 Spring MVC Controller 里写 limiter.acquire(),结果一个慢查询卡住 2 秒,后面所有请求全在排队等令牌,TP99 翻 5 倍。
立即学习“Java免费学习笔记(深入)”;
-
tryAcquire()无参版立即返回,适合“能过就过,不行就拒”的兜底策略 - 带超时的
tryAcquire(long, TimeUnit)更实用,比如设 100ms:“愿意等一下,但绝不让调用方卡死”,超时就走降级或返回 429 -
acquire()仅适合后台任务、离线计算等对延迟不敏感的场景,且必须配好线程池隔离,否则就是隐患
RateLimiter 不是全局锁,但共享实例有坑
Guava 的 RateLimiter 是线程安全的,但“线程安全”不等于“可随意共享”。多个业务逻辑共用同一个实例时,一个模块突发打满,其他模块立刻被连坐限流——这不是 bug,是设计使然。
比如订单服务和用户查询共用一个 RateLimiter.create(100.0),某次促销订单突增到 90 QPS,用户查接口立马被压到只剩 10 QPS,而它本可以稳定跑 30 QPS。
- 按业务维度拆:订单限流、支付限流、查询限流各自独立实例,避免交叉影响
- 别把
RateLimiter塞进 Spring 的 singleton bean 全局共享,除非你明确知道所有使用方语义一致 - 如果真要复用,至少用
RateLimiter.create(1.0).withBurstCapacity(10)控制单次突发上限,比裸桶更可控
和 Sentinel、Resilience4j 比,RateLimiter 缺什么
Guava RateLimiter 是个轻量令牌桶实现,没监控、没动态配置、没熔断联动、也没集群限流能力。它适合单机粗粒度保护,不适合当微服务限流主力。
线上出问题时你会发现:没地方看实时令牌余量,不能 runtime 调整 QPS,更没法和 Nacos 配置中心联动——这些都得自己补轮子,成本远超引入一个成熟框架。
- 需要指标暴露?得手动包装,定期调
getRate()+availablePermits()上报 Prometheus - 想灰度调高某接口限流值?只能改代码、发版,没法通过配置中心热更新
- 集群流量打到不同节点,每个节点独立限流,总流量可能翻倍突破预期——Guava 不解决这个问题
真正落地时,RateLimiter 最适合作为“最后一道单机兜底”,比如在 Sentinel 降级后加一层 tryAcquire() 快速拒绝,而不是从头扛起整套限流职责。


















