Skip to content

第 7 章 · 超时与重试机制

本章目标:掌握 requests 和 httpx 的超时配置与重试策略,避免请求永远挂起或无限重试。

7.1 为什么要设置超时

没有超时的请求是最常见的生产事故来源之一。想象一下:你的服务端调用第三方支付 API,对方服务器宕机但 TCP 连接不关闭——你的请求会一直挂在那里,直到耗尽所有 worker 线程,整个服务随之雪崩。

python
import requests

# 危险:没有超时,可能永远挂起
resp = requests.get('https://slow-api.example.com/data')

# 正确:设置超时
resp = requests.get('https://slow-api.example.com/data', timeout=5)

超时分为两个维度:

  • 连接超时(connect timeout):等待 TCP 握手完成的最长时间;
  • 读取超时(read timeout):等待服务器返回数据的最长时间。

7.2 requests 超时配置

requests 的 timeout 参数支持两种形式:

python
import requests

# 方式一:单一超时(连接 + 读取共享)
resp = requests.get('https://httpbin.org/delay/1', timeout=5)

# 方式二:元组(connect_timeout, read_timeout)
resp = requests.get('https://httpbin.org/get', timeout=(3, 10))
# 3 秒内建立连接,10 秒内读取响应

如果超时发生,requests 会抛出 requests.exceptions.Timeout 异常:

python
try:
    resp = requests.get('https://httpbin.org/delay/10', timeout=2)
except requests.exceptions.Timeout:
    print('请求超时')

7.3 重试机制:HTTPAdapter

requests 通过 urllib3Retry 机制实现自动重试。首先需要注册一个 HTTPAdapter

python
import requests
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter

# 创建重试策略
retry_strategy = Retry(
    total=3,                          # 最大重试次数
    backoff_factor=1,                 # 重试间隔:0s, 1s, 2s...
    status_forcelist=[429, 500, 502, 503, 504],  # 仅重试这些状态码
    allowed_methods=["GET", "POST"]   # 仅重试这些方法
)

# 挂载到 session
session = requests.Session()
session.mount('https://', HTTPAdapter(max_retries=retry_strategy))
session.mount('http://', HTTPAdapter(max_retries=retry_strategy))

# 使用 session 发送请求,自动重试
resp = session.get('https://httpbin.org/status/500')

backoff_factor 控制重试等待时间:第 N 次重试等待 backoff_factor × (2^(N-1)) 秒。设置为 1 时,重试间隔为 0、1、2、4 秒。

7.4 httpx 超时配置

httpx 的超时系统更精细,支持四种超时类型:

python
import httpx

# 方式一:单一超时
with httpx.Client(timeout=10.0) as client:
    resp = client.get('https://httpbin.org/get')

# 方式二:精细超时配置
timeout = httpx.Timeout(
    connect=3.0,    # 连接超时 3 秒
    read=10.0,      # 读取超时 10 秒
    write=30.0,     # 写入超时 30 秒
    pool=5.0        # 连接池等待超时 5 秒
)

with httpx.Client(timeout=timeout) as client:
    resp = client.get('https://httpbin.org/get')

httpx 在超时后会抛出对应的异常:ConnectTimeoutReadTimeoutWriteTimeoutPoolTimeout

7.5 重试与超时的组合策略

生产环境通常同时需要超时和重试:

python
import httpx
from httpx import Timeout

# 配置精细超时 + 重试
timeout = Timeout(connect=5.0, read=30.0, write=10.0, pool=5.0)
limits = httpx.Limits(max_connections=100, max_keepalive_connections=20)

with httpx.Client(timeout=timeout, limits=limits) as client:
    try:
        resp = client.get('https://api.example.com/data')
        resp.raise_for_status()
    except httpx.ConnectTimeout:
        print('连接超时')
    except httpx.ReadTimeout:
        print('读取超时')
    except httpx.HTTPStatusError as e:
        print(f'HTTP 错误: {e.response.status_code}')

7.6 本章小结

  • 超时是生产环境的必备防护,防止请求无限挂起;
  • requests 使用元组 (connect, read) 配置双超时;
  • httpx 提供 Timeout 对象,支持 connect/read/write/pool 四种超时;
  • 通过 HTTPAdapter + Retry 可实现智能重试,配合 backoff_factor 避免雪崩。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. requests 的 timeout 参数接受哪种格式?

2. urllib3 的 Retry 中 backoff_factor=1 时,第三次重试的等待时间是?

3. httpx 的 Timeout 不支持以下哪种超时类型?

4. 以下哪种做法最适合生产环境的 HTTP 请求?

🛠️ 动手实践

  1. 编写代码测试超时效果:访问 https://httpbin.org/delay/5,设置 2 秒超时,捕获并打印异常。
  2. 实现一个带重试的 session:对 https://httpbin.org/status/500 最多重试 3 次,间隔 1 秒,打印重试次数。
  3. 用 httpx 配置精细超时,对比四种超时异常的区别。

完成练习后,进入下一章:下一章