---
title: "重试与指数退避怎么做"
description: "重试不是把请求再发一遍那么简单。这页讲清哪些错误可以重试、哪些不要，指数退避与抖动怎么算，以及重试前为什么必须先确认幂等。"
updated: 2026-11-13
canonical: https://docai.cc/concepts/retry-backoff/
markdown_mirror: https://docai.cc/concepts/retry-backoff.md
---
## 它是什么

重试是请求失败后自动再发一次；指数退避是每次重试前等更久；抖动是在等待时间上加一点随机量。三件事合起来，才是一个不会把服务端压垮的重试策略。

它解决的问题是临时性失败：网络抖了一下、服务端短暂过载、限流窗口还没过去。这些情况再试一次大概率能成；但立刻、同时、无上限地重试，只会让情况更糟。

## 哪些错误该重试

先按状态码分类，再谈退避：

| 状态 | 含义 | 是否重试 |
|---|---|---|
| 429 | 触发限流 | 是，按响应头或退避等待 |
| 500 / 502 / 503 / 504 | 服务端临时故障 | 是 |
| 网络超时、连接重置 | 传输层失败 | 是，前提是请求幂等 |
| 408 | 请求超时 | 是 |
| 400 | 请求格式错误 | 否 |
| 401 / 403 | 鉴权失败、无权限 | 否，先修凭证和权限 |
| 404 | 地址或资源不存在 | 否 |
| 422 | 参数语义错误 | 否 |

判断标准很简单：错误是不是「这次请求本身」造成的。参数错、key 错、地址错，重试多少次结果都一样。限流本身怎么工作，见[什么是限流](/concepts/rate-limit/)。

## 怎么工作

基本形式是：每次失败后等待时间翻倍，再封顶。伪代码如下：

```text
attempt = 0
while attempt < max_attempts:
    result = call()
    if ok(result):
        return result
    if not retryable(result):
        raise result
    if attempt == max_attempts - 1:
        raise result
    delay = min(cap, base_delay * (2 ** attempt))
    sleep(random_between(0, delay))
    attempt += 1
```

几个参数要一起定：

- **最大次数**：重试次数要有上限，通常几次就够用，再多通常说明问题不在抖动。
- **总时限**：除了次数，还要有总预算。等待时间加到超过业务能容忍的上限，就停止。
- **单次超时**：每次尝试都要有自己的超时，不能因为一次卡死把整个任务拖住。
- **上限**：单次等待封顶，避免退避到几十分钟还没结束。

用「全抖动」——等待时间取零到当前上限之间的随机值——比固定间隔好。多个客户端同时失败时，不会在同一秒一起回来。

## Retry-After 与幂等

服务端返回 429 或 503 时，常会带 `Retry-After` 响应头，告诉你要等多久。它比自己的退避计算更权威，优先遵守；但如果它给出的等待超出业务预算，应该放弃并报警，而不是傻等。

重试前必须确认幂等。查询类请求重发通常安全；创建、扣费、写文件这类操作重发可能产生重复结果。LLM 调用尤其要注意：请求超时不代表服务端没在生成，重发可能重复计费，输出也会出现两份。

能带幂等键就带；不能带，就把「超时后重试」当成一次新的业务请求，记录请求 ID，接受可能重复的代价。成本怎么算，见[算清一次请求的成本](/learn/estimate-cost/)；报错怎么归类，见[读一个 API 报错](/learn/read-an-error/)。

## 常见误解

- **「所有失败都值得重试」**：确定性错误重试没有意义，还会掩盖真正的问题。
- **「重试越快恢复越快」**：服务过载时，密集重试等于补刀。
- **「固定间隔重试就行」**：所有实例同一节奏重试，会形成一阵一阵的峰值。
- **「重试只是客户端的事」**：服务端也可能重试，两端策略要一起看，否则叠加。

## 什么时候用

所有会发出网络请求的客户端都应该有这套逻辑，尤其是批处理、长任务和[流式输出](/concepts/streaming/)这类容易遇到超时的场景。最简单的做法是封装一个统一的调用函数，把重试策略放在里面，而不是散落在每个调用点。
