重试契约
客户端在写请求中发送 Idempotency-Key: <opaque value>。服务端把该键视为逻辑操作的标识:同一键与同一请求体重复到达时,不得产生第二次副作用。
CREATE TABLE idempotency_keys (
key text PRIMARY KEY,
request_hash text NOT NULL,
status text NOT NULL,
response jsonb,
created_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz NOT NULL
);
步骤
- 对写接口要求客户端提供幂等键,限制长度并拒绝缺失或格式非法的值。
- 在执行副作用之前,用唯一约束原子地写入 key、请求体哈希和 in_progress 状态;使用
INSERT ... ON CONFLICT DO NOTHING RETURNING抢占键。 - 如果键已存在且请求哈希相同,返回已存储的响应;哈希不同则返回 409,不执行副作用。
- 执行完成后存储最终状态与响应,并按客户端重试窗口设置过期时间;用相同请求发送两次验证只产生一个资源。
验证
- 第一次请求创建资源并保存响应。
- 相同键重复请求返回相同资源 ID。
- 相同键不同请求体返回 409。
- 并发重复请求只产生一次副作用。
- 过期键会被清理,不再重放。
范围与限制
只对非幂等写操作使用幂等键,例如订单、支付和 Webhook 触发的动作;GET、PUT、DELETE 按 HTTP 语义已经幂等。键是不透明的,必须限制长度,并保留得比客户端最大重试窗口更久。不要把键当作认证或授权凭据;存储完整响应时也要避免泄露敏感数据。