概述
重试不可避免:网络丢失响应、移动端超时、网关自动重发。从客户端看,超时的请求处于未知状态,可能已经成功。没有幂等机制时,“安全的重试”与“安全的业务”相互冲突:重试可能重复扣款。幂等键让服务端识别重试并返回原始结果,从而化解这个冲突。
步骤
- 建立带唯一约束的幂等键表:
CREATE TABLE idempotency_keys (
tenant_id uuid NOT NULL,
key text NOT NULL,
request_hash text NOT NULL,
state text NOT NULL,
response_status int,
response_body jsonb,
PRIMARY KEY (tenant_id, key)
);
- 在与副作用相同的事务中先声明键,再执行扣款:
INSERT INTO idempotency_keys (tenant_id, key, request_hash, state)
VALUES ($1, $2, $3, 'processing')
ON CONFLICT (tenant_id, key) DO NOTHING;
- 完成业务后把状态码与响应体写回同一行,提交事务。
- 相同键但请求体不同时返回 422 与
idempotency_key_reuse,而不是返回旧结果。 - 并发发送 5 个相同请求,断言只产生一条扣款记录。
验证
顺序重放同一键应返回 201 与相同的扣款 id,扣款表中只有一行。相同键不同金额应返回 422。5 个并发请求中,成功响应都指向同一个 id,其余请求在 processing 期间返回 409,最终 countCharges('k-2') 为 1。
注意事项
- 先写键再做业务,否则崩溃窗口会留下无键的扣款,重试将产生第二笔。
- 并发重复请求依赖
ON CONFLICT DO NOTHING的 rowCount 或SELECT FOR UPDATE才能只放行一个。 - 必须持久化状态码、响应体与影响客户端行为的响应头,重放不能重新计算。
- 键应按租户与接口隔离,避免不同操作复用同一字符串。
- 键需要保留窗口,过期后重放可能重复;清理任务应可观测。