幂等性让重复请求具有可控结果

从 HTTP 语义、业务标识和副作用记录理解幂等,并区分重放安全与操作只执行一次。

幂等性让重复请求具有可控结果

分布式调用中,客户端经常无法知道请求是否真正完成:响应可能在服务端提交之后丢失,也可能请求尚未到达就超时。客户端若重试,服务端就会看到重复操作。幂等性要求同一操作执行一次或多次,对目标资源产生的预期效果与执行一次相同;它不等于请求只运行一次。

HTTP 方法语义提供第一层约定

RFC 9110 将方法的幂等性定义在预期服务端效果上。GET、PUT、DELETE 等方法按语义具有幂等性质,而 POST 默认不具备。多次 DELETE 后返回状态可以不同,日志和计费等附带行为也可能重复,但目标资源状态应保持与一次删除一致。

方法名称本身不能修复实现。若 GET 接口实际扣减库存,它违反安全方法预期;若 PUT 每次都创建新记录,也不符合替换目标资源的语义。API 设计应让方法、资源标识和业务行为相互一致。

幂等方法允许客户端在通信失败后更有把握地自动重试,但仍要考虑并发修改和前置条件。重复 PUT 可能覆盖其他客户端的新版本,需要 ETag、版本号或条件请求保护。

幂等键标识同一个业务意图

创建、支付等操作通常使用 POST,业务效果不是天然幂等。客户端可以生成幂等键,服务端把该键与请求主体、处理状态和结果绑定。相同键再次到达时,服务端返回已有结果或等待正在进行的处理,而不是重新执行副作用。

键必须代表一次业务意图,而不是每次网络尝试。若重试时生成新键,去重无效;若不同业务动作误用同一键,后一个动作会被错误合并。服务端应验证相同键的请求参数是否一致,发现冲突时明确拒绝。

键的作用域需要定义,例如按租户、调用者和接口组合,避免不同用户碰撞。随机 UUID 常用于降低冲突,但业务天然标识如订单号也可承担作用,前提是生命周期和唯一性清楚。

去重记录需要原子提交

简单做法是先查询键是否存在,再执行写入,但并发重复请求可能都在查询后看到不存在,随后各自执行。去重检查、状态占用和业务写入需要通过唯一约束、事务或其他原子机制协调。

处理中状态也要处理崩溃。如果服务占用键后崩溃,后续请求是继续等待、接管还是标记未知,需要超时与恢复规则。若外部副作用已经发生、本地结果尚未记录,系统仍可能重复调用外部服务。

事务发件箱、外部供应商幂等键和可查询操作状态可以缩小窗口。无法原子覆盖所有系统时,应明确补偿和人工核对路径,而不是声称实现了绝对 exactly-once。

结果重放比只返回成功更完整

重复请求可能需要获得首次执行的资源 ID、状态码和业务结果。仅返回“已处理”会迫使客户端另行查询,也可能使它无法确认最初结果。去重记录可以保存响应摘要或指向规范资源。

响应中若含时间敏感令牌或个人数据,长期保存完整响应会增加安全风险。应区分可稳定重放的业务结果与需要重新生成的展示信息,并设置访问控制。

首次请求仍在处理中时,重复请求可以返回相同处理中状态或阻塞有限时间。两个请求分别得到成功和失败会使客户端难以推断,状态机应保证解释一致。

去重窗口决定重复何时失效

幂等记录不能无限保留,系统通常设置去重窗口。窗口应覆盖客户端最大重试时间、消息延迟和业务对重复的容忍范围。支付或外部提交可能需要比普通表单更长保留期。

记录过早删除后,迟到重试会被当作新操作。永久保留则增加存储、索引和隐私成本。可用业务资源的永久唯一键约束替代部分去重记录,但资源重新创建和归档规则仍要明确。

客户端和服务端需要共享重试期限。服务端窗口为一天,客户端离线一周后自动重放,就超出了保证范围。

消息消费也需要业务幂等

至少一次消息交付会产生重复,消费者应按事件 ID 或业务版本记录处理结果。只在内存中记住已见消息不能跨重启,分布式消费者还需共享或分区一致的去重状态。

同一业务事实可能由不同消息 ID 重发,单纯按传输 ID 去重仍会重复副作用。业务键、聚合版本或状态转换前置条件能提供更强保护。例如只允许订单从已支付转为已发货,重复发货事件会因当前状态被拒绝。

发送邮件、调用第三方和写多个存储时,需要让每个副作用具有独立可追踪标识。消费者数据库事务成功不代表外部动作恰好一次。

幂等不解决所有并发问题

两个不同业务意图使用不同幂等键,仍可能同时修改同一资源。库存扣减、余额转移和版本更新需要锁、原子条件或并发控制。幂等处理重复尝试,并不自动串行化合法并发请求。

请求主体中的时间、随机数或自动生成字段也可能让重复结果不同。应决定这些值由客户端固定传入,还是由服务端首次执行后保存并重放。依赖“当前时间”的状态转换需要特别记录首次处理时刻。

测试应模拟响应丢失、并发重复、处理中崩溃、窗口过期和参数冲突。只顺序调用两次并检查数据库行数不足以覆盖真实失败。

可观测性应区分尝试与业务动作

指标需要分别统计请求尝试、唯一幂等键、命中重放、冲突和最终业务结果。否则大量重试会被误认为业务增长,或者重复被隐藏而无法发现上游网络问题。

日志应带请求 ID、幂等键摘要、业务资源 ID 和处理状态,但不应泄露敏感原始键或支付信息。追踪系统可以把多次尝试关联到同一业务动作。

幂等性的边界必须写进接口契约:哪些方法或命令支持,键的作用域与窗口,参数冲突如何响应,处理中怎样查询。它让重复请求结果可控,却不能承诺网络中从未发生重复执行。

资料依据