幂等请求怎么设计:让重复操作得到同一个结果

用户双击按钮、网络重传、客户端在超时后重试,都会带来重复请求。可靠的接口不应把“请求只会来一次”作为前提,而要能够识别同一个业务意图。

本篇目标: 为一个虚构的书签创建接口设计幂等流程,分清重复传输与新的操作。

同一个意图,只对应一个结果
同一个意图,只对应一个结果放大图解 ↗

幂等键需要有作用域

只用一串随机字符作为全局键还不够。记录应至少绑定当前主体和操作类型,避免不同用户或不同接口碰巧使用同一个键。键由一次操作的发起方生成,后续网络重试保持不变。

请求参数还需要计算稳定指纹。相同键、相同指纹可以读取原结果;相同键却携带不同参数,应返回明确冲突,不能把另一个操作的结果当成成功。

键的作用域:user + operation + idempotency_key
记录内容:fingerprint + state + result_reference
可能状态:processing / completed / failed

指纹计算前要先完成字段校验与规范化。对无意义空白和字段顺序采用一致规则,同时避免把含义不同的值错误地归成一类。

并发约束放在持久层

“先查询,没有记录再插入”在并发下可能被两个请求同时通过。可以使用数据库唯一约束,让同一作用域只存在一份命令记录,然后在事务中协调状态与实际结果。

CREATE TABLE request_keys (
  actor_id text NOT NULL,
  operation text NOT NULL,
  request_key text NOT NULL,
  fingerprint text NOT NULL,
  state text NOT NULL,
  result_id text,
  PRIMARY KEY (actor_id, operation, request_key)
);

以上仅演示约束,不是完整接口实现。还要处理执行中断、处理中记录的恢复,以及结果和命令记录如何一致提交。唯一约束的作用是让冲突由数据库可靠识别。PostgreSQL:约束

回放的是结果,不是再次执行

发现命令已完成时,返回保存的结果或稳定结果引用;仍在处理中时,返回可查询的状态。不要在每次重复请求中重新发送邮件、创建外部资源或重复写记录。

幂等也不要求每次响应字节都相同。HTTP 方法的幂等语义关注重复操作对目标状态的预期影响,响应码可以因当前状态变化而不同。MDN:HTTP 方法

最后用三组用例检查:同时提交相同命令、成功后丢失响应、同键提交不同参数。再补上执行者中途退出的场景,确认系统能够查明原结果,而不是用一次新操作掩盖不确定性。

见字如晤

图解