接口超时怎么设?连接/读取/总时限三级预算与重试时长账

「超时」是采集代码里最容易被随手写一笔的参数: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 默认无超时。显式设连接 + 总时限,别依赖默认。

工程清单

  1. 三级预算:连接 2-5s / 读取 10-30s / 总时限 60s
  2. 总时限自己加(requests 不覆盖整请求)
  3. 重试总额算清:单次 × 次数 + 退避 ≤ 业务容限
  4. read timeout 用延迟分布 P99 × 倍数定
  5. 端点/市场分档
  6. 显式设超时,别依赖默认

超时预算的本质,是把「等多久」从运气变成算术:哪层超时会发、每层给多久、重试后总时长是多少,全部可算。做了这条,采集任务就很少再出现「莫名挂了 20 分钟才失败」——因为超时本来就该是预算,不是默认值。

用响应里的 elapsed_ms 建延迟分布来校准超时,字段说明见 SerpBase 官方文档。你们的超时是怎么设的?评论区聊聊「看起来没问题」的重试故事。

posted @ 2026-10-09 09:16  蜘蛛人  阅读(11)  评论(0)    收藏  举报