API 稳定性测试怎么统计?区分原始请求、重试与最终成功率
稳定性测试不能把重试后的成功当成全新成功。本文用 request_id、attempt 和终态拆分统计,解释 429、503、超时与幂等边界,并用本地模拟脚本计算最终成功率和重试放大倍数。
很多“稳定性测试”把重试后的成功直接算成成功,结果看起来比用户实际经历更好。要回答“这个 API 稳不稳定”,至少要同时保留原始请求、每次尝试和最终状态三层记录。否则一次超时后重试成功,会把一次不稳定隐藏成一次成功。
先定义三个统计单位
把一次用户或业务动作称为 request_id。它可以产生多次 HTTP 尝试,但不能因为重试就增加业务请求数。每次发出的 HTTP 请求再用递增的 attempt 编号标记,最后给原始请求一个终态:success、permanent_error 或 ambiguous。
建议同时报告:
| 指标 | 计算方式 | 用途 |
|---|---|---|
| 原始请求数 | 不同 request_id 的数量 | 代表用户真正发起了多少次任务 |
| 尝试次数 | 所有 attempt 行数 | 观察重试和流量放大 |
| 最终成功率 | 终态成功的 request_id / 原始请求数 | 反映用户最终拿到结果的比例 |
| 重试放大倍数 | 尝试次数 / 原始请求数 | 判断重试是否掩盖或放大故障 |
| 歧义数 | 结果未知、连接中断且无法确认服务端是否执行的请求数 | 决定是否需要人工对账或幂等查询 |
不要只公布“尝试成功率”。它会把同一个请求的第二次成功当成新的分母和分子,无法回答用户第一次尝试是否稳定。
哪些失败可以重试,哪些必须停下
RFC 9110 将 GET、HEAD、PUT、DELETE、OPTIONS 和 TRACE 定义为幂等方法,但幂等只描述预期效果,不保证网络重复不会产生任何外部副作用。[1] 对 POST 等非幂等请求,客户端不应仅因为连接失败就自动重发,除非应用协议提供了幂等键或其他方式证明重复是安全的。
因此记录中应有 retry_decision,而不是看到 500 就统一重试:
- DNS、连接建立或读超时:先标记为
ambiguous或retryable_error。如果请求可能已经到达服务端,重试前必须有幂等键、任务查询接口或人工确认。 - 429 或 503:读取
Retry-After,按服务端给出的秒数或日期等待;不要用固定的“睡 1 秒”覆盖服务器信号。[1] - 400、401、403、404(模型或路径错误):通常是配置或权限问题,修好原因前重试只会制造噪声。
- 解析失败:保留原始状态码、响应头和截断位置。只有确认服务端未执行且协议允许,才把它算作可重试。
异步任务也要把提交、查询和终态分开记录,可对照 API 返回 202 的异步任务排查;本文补充的是重试如何改变统计口径。若使用 Webhook,还要把事件 ID 和幂等处理与 Webhook 验签和重复处理排查 的记录合并。
最小记录格式
每一行至少保存以下脱敏字段:
{
"request_id": "run-20261111-0007",
"attempt": 2,
"started_at": "2026-11-11T14:10:03+08:00",
"status": 503,
"outcome": "retryable_error",
"retry_after": 5,
"idempotency_key": "test-0007",
"provider_request_id": "redacted",
"model": "documented-model-id"
}
不要保存 Authorization、完整提示词、Cookie 或响应中的个人数据。provider_request_id 只保留服务商允许用于工单的编号;若没有编号,写 null,不要自己编造。
用本地模拟验证统计代码
下面的脚本不访问付费 API,只用四条模拟记录演示一个请求第一次遇到 503、第二次成功,以及一个结果不确定的请求。它会同时输出“最终成功率”和“每次尝试成功率”。
python api-stability-retry-metrics-2026-11-11.py
预期结果中,原始请求数为 3、尝试次数为 4、最终成功率为 0.6667,重试放大倍数为 1.3333。如果把四次尝试直接当作样本,会错误地得到 0.5 的尝试成功率;两个数字回答的是不同问题。
代码的边界处理包括空样本、未知状态、尝试编号不连续和从 1 开始的编号。生产记录还应加上时区、网络区域、客户端版本和测试窗口,避免把不同条件混在一起。
设计一次可复核的测试
1. 固定请求和窗口
为每个模型准备不含隐私的短请求,记录模型 ID、参数、客户端版本和时间窗口。至少安排工作时段和高峰时段,但不要把两次不同请求的结果拼成一个请求。每个窗口使用相同的原始请求集合。
2. 预先写清重试上限
例如每个 request_id 最多两次尝试,遇到 429/503 时尊重 Retry-After,遇到 POST 的未知结果立即停止并保留证据。上限不是服务商承诺,也不能用来证明“无限重试安全”。
3. 分开报告四种结果
报告表至少拆成:首次成功、重试后成功、最终失败、结果不明。对“重试后成功”保留第一次失败类型;否则维护者无法看到高峰期的真实退化。
4. 观察重试放大
如果 100 个原始请求产生 180 次尝试,放大倍数是 1.8。它可能增加费用、并发和上游压力,即使最终成功率看起来很高。把这个数和 429、503、超时分别按窗口对照,才能判断重试策略是否正在制造拥堵。
5. 保存可对账证据
对 ambiguous 记录,不要自动退款或重复扣款。优先使用服务商的请求编号、任务查询或账单导出核对;没有证据时保留“未知”,不要把它改成成功或失败。账单对账可参考 API 账单导出对不上怎么办。
常见误判
| 现象 | 不能直接推出的结论 | 下一步 |
|---|---|---|
| 重试后返回 200 | 服务商稳定 | 查看首次失败和放大倍数 |
| 连接超时后没有错误账单 | 请求没有执行 | 用请求编号或账单核对 |
| 503 带 Retry-After | 等待固定 1 秒即可 | 解析秒数或日期并设置上限 |
| POST 重试成功 | 重复提交没有副作用 | 确认幂等键或业务查询 |
| 高峰期最终成功率高 | 高峰期没有退化 | 对比首次失败率、TTFB 和重试次数 |
结论
稳定性测试的最小可信单位是“原始请求 + 尝试序列 + 终态”。先把 429、503、超时和未知结果分开,再决定是否重试;最后同时报告最终成功率和重试放大倍数。这样才能把一次暂时恢复、一次重复提交和一次真正稳定区分开。
资料
- RFC 9110:HTTP Semantics,第 9.2.2 节和第 10.2.3 节:幂等方法与
Retry-After。 - HTTPX 超时文档:连接、读取、写入和连接池超时的区别。