← 中文资源

简体中文版本

HTTPX 异步客户端重试传输层

继承 HTTPX 传输层实现重试:仅幂等方法可重试,指数退避封顶加随机抖动,429 响应优先遵循 Retry-After 头。

阅读英文原文

示例说明

把重试做进传输层的好处是对上层完全透明:业务代码照常发请求,传输层负责瞬态故障的自愈。判定逻辑双管齐下——方法幂等性与响应状态码共同决定是否重试;限流响应优先读取服务端建议的等待时长,没有才用本地指数退避,并对上限封顶防止退避爆炸。

技术要点

  • 传输层重试对业务代码透明
  • Retry-After 头优先于本地退避算法
  • 随机抖动防止客户端同步重试

代码解读

RetryingTransport 继承 AsyncHTTPTransport 并覆写请求处理:循环里先算退避再发请求,可重试状态码且方法幂等时 continue 进入下一轮,传输错误被捕获继续循环;耗尽次数后抛出带最后一次错误详情的异常。main 演示把自定义传输注入客户端。

使用建议

写操作重试必须配幂等键。重试次数与超时预算要统筹,防止总耗时超过上游容忍。连续重试失败应熔断而非无限循环。

代码示例

import asyncio
import random

import httpx


class RetryingTransport(httpx.AsyncHTTPTransport):
    def __init__(self, retries: int = 3, **kwargs):
        super().__init__(**kwargs)
        self._retries = retries

    async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
        retryable = request.method in {'GET', 'HEAD', 'OPTIONS'}
        last_error: Exception | None = None

        for attempt in range(self._retries + 1):
            if attempt > 0:
                backoff = min(2 ** attempt * 0.5, 8.0)
                await asyncio.sleep(backoff + random.uniform(0, 0.3))
            try:
                response = await super().handle_async_request(request)
                if response.status_code in {429, 502, 503, 504} and retryable:
                    retry_after = response.headers.get('Retry-After')
                    if retry_after and retry_after.isdigit():
                        await asyncio.sleep(int(retry_after))
                    continue
                return response
            except (httpx.TransportError,) as exc:
                last_error = exc
        raise httpx.TransportError(f'exhausted retries: {last_error}')


async def main() -> None:
    transport = RetryingTransport(retries=3)
    async with httpx.AsyncClient(transport=transport, timeout=10.0) as client:
        resp = await client.get('https://api.example.com/status')
        print('final status:', resp.status_code)


asyncio.run(main())