一个接口的容量预算,QPS、超时、重试、线程池该怎么一起算?
接口容量预算最怕把 QPS、timeout、retry 和线程池拆开各算一遍。只有把入口流量、线程持有时间、重试放大量和下游退化场景放进同一张账里,预算结果才真的能指导限流、线程池 sizing 和高峰保护。
接口容量预算常见的失败,不是因为没人算,而是每个人只算了自己手里的那一段。流量同学盯 QPS,应用同学盯 timeout,框架同学盯线程池,结果每个数字单看都说得过去,拼到一次高峰或一次下游抖动里,系统还是会排队、超时、重试连着来。
真正决定接口能不能扛住高峰的,通常是几笔账同时叠在一起:
- 入口流量到底有多大
- 请求在系统里会停留多久
- timeout 后会不会再来一轮 retry
- 线程、连接和下游额度会被占多久
- 下游一慢时,真实调用量会被放大到什么程度
所以容量预算真正该回答的,不是“平时能跑多少 QPS”,而是另外两个更接近事故现场的问题:
- 高峰时这些请求会在系统里堆多久
- 某一层一慢,原始流量会被放大成多大的真实压力
本文就围着这两件事展开,把 QPS、timeout、retry、线程池重新放回同一套预算口径里。
你手上的问题是不是容量预算
如果你现在要做预算,第一轮别只抓一个参数,而是把原始入口 QPS、线程持有时间、timeout 预算、retry 放大量和下游退化时的真实并发占用摆到同一张纸上。这样你才知道自己算的是“平时数字”,还是“高峰下真的会出事的那部分”。
| 你手上的问题 | 先看哪里 | 为什么 |
|---|---|---|
| 想给一个接口做容量预算,把 QPS、timeout、retry、线程池放进同一套口径 | 看完本文再细化 | 这里先把最容易被拆散的几笔账重新并到一套模型里 |
| 你正在设计 timeout、retry、熔断参数的协同关系 | 一个接口的超时、重试、熔断参数,应该怎样整体设计? | 那篇更适合单独梳理保护参数怎么互相影响 |
| 你要把线程池和数据库连接池一起做联合预算 | 线程池和数据库连接池的容量,为什么要一起做预算? | 那篇专门补资源池互相卡住时该怎么算 |
| 现场已经在慢 / timeout,想知道先查哪层 | 如何建立接口慢 / 超时专题的值班检查表? | 先收敛故障现场,再回头补预算更靠谱 |
这篇主要算清哪几笔账
如果你现在是在高峰前做容量核算,或者刚经历过一次 timeout / retry 放大的故障,本文优先回答两个问题:
- 高峰时请求会在系统里停多久
- 某一层一慢,原始流量会被放大成多大的真实压力
如果你的现场已经在慢、在 timeout,本文不能替代第一轮排障;如果你主要在调 timeout / retry / 熔断参数,也该先去看参数设计那篇。这里的重点只有一个:把容量预算本身算清,再把结果落回线程池、连接池和保护阈值。
一、为什么只看 QPS,容量预算很容易失真
因为 QPS 只回答了:
- 单位时间来了多少请求
但没回答:
- 每个请求会占资源多久
- 超时之后会不会衍生出更多请求
- 线程是不是会被慢下游一起拖住
举个很典型的例子。
两个接口可能都是 1000 QPS:
- A 接口单次 50ms,基本不重试,线程持有时间短
- B 接口单次 300ms,偶发 timeout,retry 1 次,线程还会等数据库连接
表面上 QPS 一样,真实容量压力却完全不在一个量级。
所以更接近真实工程问题的容量预算,应该至少同时看:
- 流量规模
- 请求停留时间
- 失败后的放大量
二、为什么 QPS、timeout、retry、线程池必须一起看
因为它们在一条调用链里本来就是互相作用的。
1. QPS 决定有多少请求在进来
这是入口规模。
2. timeout 决定你允许一个请求最多占资源多久
timeout 越长,请求可能占线程、连接和下游资源的时间就越久。
3. retry 决定一次失败会不会放大成多次尝试
retry 一旦存在,真实调用量就不再等于原始流量。
4. 线程池决定系统能同时吞下多少个“正在占线程的请求”
线程池不是只和 QPS 有关,更直接和:
- 请求停留时间
- 慢下游等待
- 重试后的额外尝试数
有关。
所以如果把这四项拆开看,最后很容易出现下面这种错位:
- QPS 看起来不高
- timeout 配得挺保守
- retry 也不算太多
- 线程池数字也不小
但拼在一起以后,系统依然会在高峰或依赖抖动时突然排队。
三、先建立一个最基本的容量认知:吞吐不只取决于流量,还取决于占用时间
这几乎是所有容量预算里最值得先记住的一句话。
如果单个请求在线程里停留的时间变长,那么即使入口 QPS 不变:
- 同时占住线程的请求数也会变多
- 线程池更容易打满
- 后续请求会更早排队
所以真正决定线程池压力的,不只是 QPS,而更像:
- 同时在场的请求数
而同时在场的请求数,又和:
- QPS
- 单次线程持有时间
直接相关。
这也是为什么 timeout、retry、慢下游和线程池一定要一起看。
四、容量预算时,最该拆清楚哪几个量
如果团队想认真做一个接口的容量预算,我更建议先把下面 5 个量拆开。
1. 原始入口 QPS
重点先放在:
- 平均 QPS
- 峰值 QPS
- 突发窗口 QPS
- 大促或活动窗口 QPS
注意不要只看日常峰值,还要看:
- 是否有瞬时尖峰
- 是否有批量重放或补偿流量
2. 单请求线程持有时间
这比单看接口 RT 更值钱。
因为真正吃掉线程池的,不是用户体感时间本身,而是:
- 请求从拿到线程到释放线程,一共占了多久
这里尤其要看:
- 平均值
- P95 / P99
- 高峰时是否明显拉长
3. timeout 预算
你要明确:
- 客户端整体愿意等多久
- 网关愿意等多久
- 应用整体预算是多少
- 关键下游单次 timeout 是多少
如果这层不清楚,后面 retry 和线程池都没法算准。
4. retry 放大量
重点不是“配了几次”,而是:
- 哪些失败会触发 retry
- timeout 时最坏会多出多少调用
- 是否多层 retry 叠加
- 是否有退避和抖动
很多团队容量预算完全没把 retry 放进去,结果纸面流量和真实流量根本不是一回事。
5. 下游退化场景下的线程持有时间
这一步最容易被忽略,却最重要。
你不能只看正常时:
- 接口 80ms
- 线程池没问题
更要看:
- 如果下游从 80ms 抬到 400ms,会发生什么
- 如果连接池等待起来了,会发生什么
- 如果 retry 启动了,最坏同时在场请求数会涨到多少
没有退化场景,容量预算就只能说明“平时还行”。
五、容量预算别一上来就套线程池公式
我见过不少容量评估一上来就套线程池公式,但真到高峰翻车时,问题往往出在前面的账根本没对齐。更靠谱的做法,是先把总预算、放大量和并发占用接起来。
第 1 步:先定总预算
先回答:
- 用户可以等多久
- 业务允许失败快返回还是必须多等
- 核心链路和非核心链路的时间预算分别是多少
这一步决定 timeout 的大框架。
第 2 步:再算放大预算
也就是:
- retry 最多能把调用量放大到多少
- timeout 和 retry 叠加后,单个用户请求最坏会变成几次内部调用
- 网关、SDK、代码层是否存在多层重试
这一步决定真实流量不是原始 QPS,而是:
- 原始 QPS x 放大系数
第 3 步:最后才算并发预算
也就是:
- 在真实调用量和最坏持有时间下,同时在场的请求数会有多少
- 线程池、连接池和关键下游是否一起扛得住
这样做的好处是:
- 线程池数字不是拍出来的
- 而是从流量、时间和放大量自然推出来的
六、一个典型例子:为什么 800 QPS 的接口也能把线程池打满
假设某个接口日常峰值只有 800 QPS,看起来不算夸张。
正常时:
- 单请求线程持有 80ms
- timeout 很少
- retry 几乎不触发
这时线程池压力确实不大。
但某天活动时,下游库存服务抬到 500ms,且 timeout 设在 400ms,retry 1 次。
现场会发生什么?
- 第一轮请求会在 400ms 左右超时
- retry 再打一轮
- 线程持有时间从 80ms 接近变成数百毫秒,甚至更长
- 真实内部调用量明显高于原始 800 QPS
- 同时在场请求数暴涨
这时你会看到:
- 原始 QPS 并不吓人
- 线程池却已经明显排队
因为真正把线程池打满的,不是单一的 QPS,而是:
- QPS
- timeout
- retry
- 下游退化
一起作用后的结果。
七、线程池预算里最容易漏掉的 3 个问题
1. 只按正常 RT 算,不按退化 RT 算
这会让容量预算在真正出问题时几乎没有参考价值。
2. 忽略 retry 带来的额外线程占用
每次 retry 都是新的线程和依赖占用,不是“免费再试一次”。
3. 把线程池和依赖容量分开看
如果下游连接池、数据库连接池或外部接口承受不了,单独放大线程池只会更快把请求压到堵点上。
八、容量预算最后要落到哪些工程动作上
一套接口容量预算,如果最后只产出一个 PPT 数字盘子,价值是不够的。
更应该落到下面这些动作。
1. timeout 设计
- 哪些链路 timeout 要更短
- 哪些链路要快速失败
- 哪些链路要给重试留预算
2. retry 设计
- 哪些错误可重试
- 最多重试几次
- 是否必须加退避和抖动
- 哪些写接口禁止默认 retry
3. 线程池与连接池 sizing
- 核心链路单独配池
- 慢任务和异步任务要隔离
- 线程池和连接池一起做联合预算
4. 保护策略
- 限流阈值设在哪
- 熔断何时触发
- 降级哪些非核心逻辑
- 高峰时优先保护哪些入口
也就是说,容量预算最终应该服务接口保护,而不是只说明“理论上能跑多少”。
九、关键误判
误判 1:接口容量预算就是看峰值 QPS
QPS 只是入口规模,不代表真实资源占用压力。
误判 2:timeout 只影响失败体验,不影响容量
timeout 直接决定请求最多会占线程和依赖多久,本身就是容量变量。
误判 3:retry 只是兜底逻辑,不用进容量模型
一旦 retry 触发,真实调用量和线程占用都可能明显放大。
误判 4:线程池够大,接口容量就够了
如果下游、数据库连接池或回源链扛不住,大线程池只会更快把压力压到更深层。
十、FAQ:做接口预算时最容易卡住的几个问题
1. 容量预算到底先看 QPS 还是先看线程池?
都要看,但顺序上更建议先看流量和时间预算,再看线程池和依赖容量。
2. 为什么接口 RT 正常,容量预算也可能仍然不稳?
因为平时 RT 正常不代表高峰和退化场景下的线程持有时间、retry 放大量和依赖承压也稳。
3. retry 应该怎么进容量模型?
至少要把:
- 触发条件
- 最坏重试次数
- 多层重试叠加
- 最坏调用放大量
一起算进去。
4. 容量预算和限流、熔断是什么关系?
容量预算给出边界,限流和熔断负责在逼近边界时保护系统别继续放大。
十一、最后总结:容量预算不是报一个 QPS 数字,而是把最坏场景提前算出来
如果预算只停在 QPS,很容易过分乐观;如果只盯线程池,又会漏掉 timeout 和 retry 带来的真实放大。接口容量真正需要的,是把入口流量、时间预算、重试放大和退化场景一起算到同一张账里。
更实用的顺序仍然是:先定接口总时间预算,再算 retry 带来的最坏放大量,最后结合线程持有时间、线程池、连接池和下游退化场景去估并发占用。这样得出来的结果,才真的能拿去指导接口保护、线程池 sizing 和高峰期稳定性治理。
这篇归在“稳定性治理与值班体系”专题里。看完预算模型之后,后面通常会分成三类问题继续往下补。
先把接口保护参数定清
资源池已经开始一起吃紧
准备把预算结果沉到治理动作
如果你还停留在参数表和峰值 QPS,就先把 timeout、retry、熔断这层补完整;如果线上已经出现线程池、连接池和下游一起变差,就优先把资源池和放大量画到一张图里;如果已经准备推进值班治理,再把这套预算结果沉到专题治理和检查表里。