【背景】
上个月我们一个支付回调接口被上游突发流量打爆,QPS 从稳态 2000 飙到 18000。当时的限流配置是网关层按 QPS 切的(Sentinel 集群限流,阈值 5000 QPS),结果诡异的事情发生了:
- 网关侧看,QPS 确实被压到了 5000 以下,"限流生效了";
- 但下游 MySQL 连接池(HikariCP max=50)在限流触发后 3 秒内 pending 线程堆到 200+,P99 从 8ms 飙到 2200ms;
- 最终不是被 QPS 打挂的,是被"并发连接数 × 每连接持锁时间"打挂的。
事后复盘才意识到:我们的流量特征不是"均匀小请求",而是"少量大请求(含事务内多次 DB 交互)+ 大量小请求"混合。按 QPS 切等于把"1 个重请求 = 1 个轻请求",粒度太粗了。
【常见分歧 / 几种做法】
我梳理了身边几个团队的实践,大致分三派:
派系 A:QPS 一刀切(最常见)
- 网关层 / 应用层按 QPS 限流,简单直接,运维成本最低。
- 代表配置:Sentinel QPS 阈值、Nginx limit_req。
- 隐含假设:每个请求的资源消耗大致均匀。
派系 B:并发连接数 / 在途请求数
- 按"同时在处理中的请求数"(in-flight)限流,和连接池大小对齐。
- 代表实现:Go 的 semaphore / Java 的 Semaphore + CompletableFuture、Nginx limit_conn。
- 优点:天然和下游资源(DB 连接、线程池)对齐。
- 缺点:请求耗时波动大时,"在途数"本身就不稳定,阈值不好定。
派系 C:按资源消耗(加权 / 信用分)
- 给不同接口 / 不同请求分配不同的"资源权重",限流时按权重扣减。
- 代表实现:Sentinel 的"线程数规则"+ 自定义权重、某些自研的 credit-based 流控。
- 优点:最贴近真实资源消耗。
- 缺点:权重怎么定?谁来定?流量模式一变权重就失效,维护成本极高。
还有一种"混合派":网关按 QPS 做粗粒度保护,应用层按并发数做细粒度保护,DB 层靠连接池兜底。本质上是"多层防御",但每层的阈值联动谁来维护?
【我的倾向与理由】
我偏向 B + 少量 A 兜底,理由:
- 并发数(in-flight)和下游资源天然对齐。HikariCP max=50,那我的 in-flight 上限就不该超过 50(考虑事务内多次 DB 交互,可能要打个 0.7 折扣)。这个映射关系是确定性的,不需要猜。
- QPS 是一个"结果指标",不是"原因指标"。同样的 5000 QPS,如果平均耗时 5ms 和平均耗时 200ms,对下游的压力差 40 倍。用 QPS 限流等于用"流量"近似"负载",在请求耗时不均匀时失真严重。
- 资源加权(派系 C)看起来最精确,但实际维护成本最高。我见过两个团队做了加权流控,上线三个月后权重表没人更新,最后退化成了"看起来有加权、实际是拍脑袋"。高并发系统的配置应该"少而稳",而不是"多而精"。
但我也承认 B 的弱点:当请求耗时本身波动很大时(比如有 10ms 的查询也有 2s 的报表查询),"在途数"这个指标会剧烈震荡,阈值很难定。这时候 C 的思路确实有价值——只是我不想维护一张权重表,我更希望系统能根据"实际资源水位"(连接池 pending 数、线程池队列深度)做自适应。
【想听大家聊聊】
几个具体问题抛给大家:
- 你的生产环境限流到底切在哪一层?网关 / 应用 / 连接池 / 线程池——是"只切一层"还是"多层防御"?
- 有没有团队真正落地过"按资源消耗加权"的限流?权重表怎么维护、多久更新一次、流量模式变了怎么办?
- 对于"请求耗时极度不均匀"的接口(比如同一个 endpoint 既有 5ms 的查询也有 2s 的聚合),你们怎么限?
- 连接池 pending 数 / 线程池队列深度,有没有团队把它当作"自适应限流"的反馈信号?阈值怎么定?
我猜大多数团队的真实答案是:"网关 QPS + 连接池兜底,中间那层没细想"。但我想听那些真的在"中间层"做过文章的人怎么讲。
0赞
0收藏
0楼层