API 错误处理与重试策略:给接口定一套“讲得清”的错误语言

接口报错是前后端最常见的沟通场景,可很多站点把错误处理做成了“黑盒”:要么返回一串 HTML 错误页,要么只给一个干巴巴的 500,前端拿到之后只能靠猜。问题通常不在某一个错误码,而在没有一套统一约定。好的错误设计应该让调用方一眼看懂三件事:错在哪一层、为什么错、能不能重试。

错误分层:先分清是客户端的错还是服务端的错

状态码 含义 常见触发场景 是否应重试
400 请求格式错误 JSON 解析失败、缺必填字段
401/403 未认证/无权限 token 过期、签名错误 否(刷新凭证后重发)
404 资源不存在 路径拼错、资源已删除
422 语义校验失败 邮箱格式不对、库存不足
429 限流 超出配额或频率限制 是(遵循 Retry-After)
500/502/503/504 服务端异常 崩溃、网关超时、上游不可用 是(有限次数)

统一的错误响应结构

4xx 和 5xx 都应该返回相同的 JSON 结构,而不是一段随意的文本。

{
  "code": "RATE_LIMIT",
  "message": "too many requests",
  "request_id": "abc123",
  "retry_after": 3,
  "details": {"limit": 100, "window": "1m"}
}
  • code:机器可读的错误码,前端可以据此做分支判断
  • message:给人看的说明
  • request_id:贯穿日志的关键,排查问题就靠它
  • retry_after:429 时返回建议等待秒数,比客户端自己猜更准确

重试策略:不是所有错误都值得重试

重试不是“报错就再调一次”。不节制的重试会把一次小故障放大成雪崩——服务端越忙,客户端越疯狂重试,结果越忙。记住两个原则:只重试可恢复的错误,每次重试之间要等待。

参数 建议值 说明
可重试错误 429、502、503、504 400/401/403/404/422 一律不重试
初始等待 1s 指数退避的起点
退避系数 2 1s → 2s → 4s → 8s
最大等待 30-60s 防止间隔无限拉长
重试上限 3 次 超过即失败,交给上层处理
抖动(jitter) 随机 ±20% 避免客户端在同一时刻齐发

带抖动的指数退避是一个很小的改动,却能显著降低重试风暴的发生概率。客户端最好同时尊重服务端返回的 Retry-After 响应头,它比本地算出来的时间更准。

幂等键:让“重试”不会造成重复下单

POST 类请求重试最大的风险是副作用被重复执行——用户点了两次“提交订单”,结果下了两单。解决办法是幂等键:客户端在请求头里带一个唯一值,服务端记录这个键,遇到重复键直接返回第一次的结果。

Idempotency-Key: 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d

场景:支付回调连着报了三次错

一个典型的排查案例:某天支付回调接口开始连续返回 502,支付服务商的重试机制触发了三次都失败,最后任务被丢进了死信队列。开发人员顺着日志里相同的 request_id 一路查到网关,发现是上游某台机器内存被打满。整个过程里,因为错误结构统一、request_id 齐全,从发现问题到定位只花了十几分钟。如果当时错误响应是乱糟糟的文本,这个排查时间至少要翻三倍。

客户端侧还要做什么

  • 在网关或 SDK 层统一封装重试,业务代码里不要到处写 for 循环重试
  • 加熔断器:连续失败达到阈值(如 5 次)后,短时间内直接快速失败,不再发请求
  • 超时设置:连接超时和读超时分开放,默认 5s/10s,避免慢接口拖死整个页面

错误码命名与接口文档约定

有了统一结构,还要让错误码本身可读。建议用“领域 + 场景 + 状态”的方式命名,例如 ORDER_INSUFFICIENT_STOCKPAYMENT_CARD_DECLINEDAUTH_TOKEN_EXPIRED,比裸数字 1001 好懂得多。错误码一旦发布就尽量保持稳定,客户端通常会对特定 code 做硬编码分支,改名会破坏兼容。

接口文档里应该为每个错误码写清楚:触发条件、message 的模板、是否需要重试、有没有关联的 retry_after。可以生成一份统一的“错误码参考页”,前后端都以它为准。实践中,很多团队把错误码表放在接口文档(如 OpenAPI)的顶部,并用自动化测试断言“所有 4xx 都返回相同结构、都带 request_id”。

另一个常被忽略的约定是日志联动:每个错误响应都要把 request_id 和对应日志关联起来,方便从用户截图里的报错反查后端。建议在入口中间件里统一注入 request_id,并把它透传到下游服务,形成一条完整的调用链。

参考:Stripe API 错误处理文档 https://docs.stripe.com/api/errors
参考:Google Cloud API 错误模型 https://cloud.google.com/apis/design/errors
参考:RFC 6585(429 Too Many Requests) https://www.rfc-editor.org/rfc/rfc6585