在设计幂等接口时,幂等键(Idempotency Key)的位置选择——请求头还是请求体——常引发争论。本文从重试链路的实际行为出发,分析两种方案的适用场景,并给出可落地的设计建议。
重试链路的典型形态
客户端重试通常有两种模式:
- 自动重试:由HTTP客户端库、RPC框架或网关在超时后自动重发。此时请求已构造完毕,重试时通常原样发送。
- 手动重试:业务代码捕获异常后重新调用,可能重新构造请求参数。
无论哪种模式,幂等键的核心要求是:同一次业务操作的所有重试必须携带相同的键。
请求头 vs 请求体:关键差异
放在请求头(如 Idempotency-Key: <uuid>)
- 优点:与业务参数解耦,便于网关、中间件统一拦截处理;符合HTTP语义(元数据放头部);客户端重试时只需保持头部不变,无需修改请求体。
- 缺点:需要额外解析头部;部分老旧客户端或调试工具可能忽略自定义头;若通过消息队列等非HTTP协议传输,头部概念不适用。
放在请求体(如 JSON 中的 idempotency_key 字段)
- 优点:协议无关,HTTP、gRPC、消息队列均可使用;参数集中,便于业务代码直接读取;调试时可见。
- 缺点:与业务参数混合,可能被误修改;若请求体较大或为流式数据,解析成本略高;网关层需解析body才能获取,影响性能。
从重试链路反推设计位置
选择的关键在于重试时哪些部分会被原样保留。
- 如果重试由基础设施(如HTTP客户端、服务网格)自动发起,请求头和请求体通常都会原样重发,两者皆可。
- 如果重试由业务代码手动发起,且可能重新构造请求体(例如从数据库重新读取订单信息),那么放在请求体中的幂等键有被覆盖或遗漏的风险。此时放在请求头更安全,因为业务代码通常不会修改头部。
- 如果接口不仅通过HTTP暴露,还通过消息队列或RPC调用,则放在请求体(或消息属性)更通用。
实践建议
- HTTP API 优先使用请求头:如
Idempotency-Key,这是业界常见做法(参考 Stripe、PayPal 等公开文档)。 - 非HTTP协议或混合协议使用请求体字段:如 gRPC 的 metadata 或消息体中的
request_id。 - 无论放在哪里,都要在服务端做唯一性校验:通常用 Redis 或数据库唯一索引,记录键与处理结果,并设置合理的过期时间。
- 明确重试策略:客户端应保证同一业务操作的重试使用相同键,服务端应返回相同结果(或明确冲突)。
- 文档化:在API文档中清晰说明幂等键的位置、格式和有效期。
总结
幂等键的位置不是风格问题,而是由重试链路决定的工程决策。自动重试场景下两者均可,手动重试或跨协议场景下需根据实际情况选择。核心原则是:确保重试时键不变,且服务端能可靠识别。