接口超时怎么设?连接/读取/总时限三级预算与重试时长账
「超时」是采集代码里最容易被随手写一笔的参数:timeout=30 挂上去就不管了。直到有次排障才发现——团队里每个请求的超时都不一样,重试叠加之后总耗时完全不可控。超时要像预算一样设计:从「发起请求」到「用户放弃」之间,每一层分配多少时间,写清楚、算总账。这篇讲三级超时预算怎么定。
三级预算的结构
一次 HTTP 请求的耗时拆成三段,每段一个超时:
| 层级 | 含义 | 典型值 | 超时说明什么 |
|---|---|---|---|
| 连接超时 | TCP/握手建立 | 2-5s | 连不上=网络/路由问题,别等 |
| 读取超时 | 多次数据包之间无新数据 | 10-30s | 服务端处理慢/无响应 |
| 总时限 | 整个请求的上限 | 60s | 兜底:防止任何一层挂死 |
代码对应(requests 语义:timeout=(连接, 读取),循环做总时限):
import time
TIMEOUT = (5, 30) # (connect, read)
def fetch_with_budget(url, payload, total_budget=60):
start = time.monotonic()
resp = requests.post(url, json=payload, timeout=TIMEOUT, stream=True)
# 大响应手动推进读取,并在循环里检查总时限
...
elapsed = time.monotonic() - start
if elapsed > total_budget:
raise TimeoutError(f"超过总时限 {total_budget}s,实际 {elapsed:.1f}s")
关键认知:requests 的 timeout=(a,b) 只覆盖「建立连接」和「两个包之间」,不覆盖整个请求的总时长。传输中途一直有包的话,read timeout 不会触发——大响应场景尤其要自己加总时限。
重试的时长账(最容易被忽略)
超时预算必须把重试算进去:
总时长 ≈ 第1次尝试 + 退避 + 第2次 + 退避 + 第3次 ...
例子:3 次尝试,每次预算 60s,退避 1/2/4s → 最坏总时长 ≈ 3×60 + 7 = 187 秒(3 分钟)。这还没算失败本身的时间。
预算公式:可用时间分给「单次 × 次数」。如果业务要求 60 秒内出结果,就不能是「3 次 × 60s」——要么减次数(1 次 + 退避)、要么减单次预算(20s × 3)。
怎么定「读取超时」:用延迟分布,不拍脑袋
读取超时的值要有数据支撑。先采一批请求的 elapsed_ms 画直方图:
- 正常慢请求的 P99 是多少?
- 故障时的延迟分布长什么样?
# 用接口响应里的 elapsed_ms 建分布
times = [data["elapsed_ms"] for data in collected]
p99 = sorted(times)[int(len(times) * 0.99)]
print(f"P99 = {p99}ms")
# 读取超时 ≈ 正常 P99 × 3~5 倍(留足余量,又能挡故障)
超时设在「正常慢的几倍」而不是「拍脑袋的 30」——既能放过偶发慢请求,又能在真故障时快速失败。
不同端点别用同一档
图片、地图端点通常比搜索慢,市场不同延迟也不同。预算按端点/市场分档,别一个数字全通吃(多市场篇的表格思路在这里复用)。
踩坑记录
坑 1:全请求共用一个超时。 慢端点被误杀、快端点白等。按端点/市场分档。
坑 2:重试预算没算。 「3 次重试」让总时长变成 3 分钟。预算 = 单次 × 次数 + 退避,总账先算。
坑 3:connect/read 混成一个数。 连不上和读不完是两类问题,混成一个无法归因。分开设。
坑 4:把 read timeout 当总超时。 大响应持续有包时,read timeout 不触发,卡到天荒地老。自己加总时限。
坑 5:超时值拍脑袋。 没有延迟分布支撑,设小误杀、设大失控。用 P99 × 倍数定 read。
坑 6:同步脚本里 socket 挂死。 某些底层 socket 默认无超时。显式设连接 + 总时限,别依赖默认。
工程清单
- 三级预算:连接 2-5s / 读取 10-30s / 总时限 60s
- 总时限自己加(requests 不覆盖整请求)
- 重试总额算清:单次 × 次数 + 退避 ≤ 业务容限
- read timeout 用延迟分布 P99 × 倍数定
- 端点/市场分档
- 显式设超时,别依赖默认
超时预算的本质,是把「等多久」从运气变成算术:哪层超时会发、每层给多久、重试后总时长是多少,全部可算。做了这条,采集任务就很少再出现「莫名挂了 20 分钟才失败」——因为超时本来就该是预算,不是默认值。
用响应里的 elapsed_ms 建延迟分布来校准超时,字段说明见 SerpBase 官方文档。你们的超时是怎么设的?评论区聊聊「看起来没问题」的重试故事。

浙公网安备 33010602011771号