超时:三层时间都要设
只设一个「总超时」往往不够。通常分三层:连接超时(1–3 秒,用于建立 TCP/TLS)、读取超时(按接口 P99 设定)、整体重试预算(所有重试累计不超过上限)。没有预算约束时,一次抖动可能引发数分钟的重复请求,反而把下游压垮。
重试:只重试「可能成功」的失败
| 失败类型 | 是否重试 | 建议 |
|---|---|---|
| 连接超时、502 / 503 / 504 | 适合 | 指数退避 + 抖动 |
| 429(限流) | 谨慎 | 遵循 Retry-After,并限制次数 |
| 408 请求超时 | 视场景 | 必须配合幂等键 |
| 400 / 401 / 403 / 404 / 422 | 不要 | 请求本身有问题,重试无意义 |
| 500 | 谨慎 | 仅在确认是瞬时故障时 |
指数退避与抖动
// 第 n 次重试的等待时间(毫秒),带随机抖动以避免重试风暴
const base = 200, cap = 10000;
const wait = Math.min(cap, base * 2 ** attempt);
const jitter = Math.random() * wait * 0.3;
await sleep(wait + jitter);
没有抖动的退避会让所有客户端在同一时刻重试,形成重试风暴;抖动把重试分散到一个时间窗口内,显著降低峰值。
幂等:让重试变得安全
幂等指同一请求执行多次与执行一次的结果相同。缺少幂等保证时,重试可能造成重复扣款、重复下单或重复发消息。常见做法:
- 客户端为每次业务操作生成幂等键(如 UUID),随请求一起发送;
- 服务端记录「幂等键 → 首次结果」,重复请求直接返回该结果而不重复执行;
- 幂等键设置有效期(如 24 小时),避免存储无限增长;
- 写接口尽量设计为「设置某值」而非「增加某量」(例如 setBalance 优于 addBalance)。
四个常见坑
- 重试放大流量:没有次数上限与整体预算,一次抖动会被放大成雪崩;
- 多层同时重试:网关层重试 3 次、应用层再重试 3 次,实际请求变成 9 次;
- 对非幂等接口重试:直接造成资金或消息重复;
- 超时设置过长:连接被占满、线程池耗尽,故障扩散得更快。
可观测性与排查
为每次调用带上请求 ID(可用 UUID 生成),并记录尝试次数、耗时与最终状态;出问题时先看「重试次数分布」与「耗时分布」,通常能立刻区分是网络抖动、下游变慢,还是重试策略本身不合理。
动手试试:UUID 生成(生成请求 ID 与幂等键)