接口幂等设计:从重复提交到可恢复业务状态机
网络请求可能超时,但超时只说明客户端没有及时收到结果,不代表服务端没有执行。用户重复点击、网关重试、消息重复投递和客户端断线重连都会制造重复操作。幂等设计的目标,是让同一业务意图执行多次仍只产生一次有效结果。
先区分读取与写入
GET、PUT、DELETE 在 HTTP 语义上通常应幂等,但业务实现仍可能破坏这一点,例如每次 GET 都增加余额。POST 默认不幂等,创建订单、支付、提交审核等操作需要显式业务键。
客户端为一次业务意图生成 Idempotency-Key,并在重试时复用:
Idempotency-Key: 6a1d3a0e-...
服务端保存 Key、请求摘要、处理状态和响应结果。相同 Key 且请求内容一致时返回第一次结果;相同 Key 但参数不同,应拒绝,避免错误复用。
数据库唯一约束是最后防线
CREATE TABLE idempotency_record (
scope VARCHAR(64) NOT NULL,
request_key VARCHAR(128) NOT NULL,
request_hash CHAR(64) NOT NULL,
state VARCHAR(16) NOT NULL,
response JSON NULL,
create_time DATETIME NOT NULL,
PRIMARY KEY (scope, request_key)
);
首次请求插入 processing,业务结果与记录状态在同一事务中提交。唯一键冲突后读取已有记录:completed 返回保存结果;processing 返回处理中或短暂等待;failed 是否允许重试要由业务规则决定。
仅用 Redis SETNX 加短 TTL 存锁有风险:业务仍执行时 Key 过期,第二次请求会再次进入;Redis 故障也可能丢保护。Redis 可以加速判断,但重要写操作仍应由数据库唯一约束或领域唯一键兜底。
幂等键应该绑定什么
- 绑定用户或租户,防止不同用户 Key 碰撞。
- 绑定接口与业务动作,不能跨操作复用。
- 保存请求摘要,检测同 Key 不同参数。
- TTL 至少覆盖客户端最大重试窗口和消息最长重投周期。
- 对永久业务对象,直接使用业务唯一键可能比单独幂等表更自然。
例如“用户领取某活动奖励”可直接建立 (activity_id, user_id) 唯一键;无论请求 Key 如何变化,都只能成功一次。
状态机比布尔成功更可靠
跨服务流程可能经历 created -> processing -> succeeded/failed。客户端查询同一业务 ID 获取状态,后台任务从中间状态继续。不要在超时后直接创建一个新业务对象,否则无法判断上一次是否已成功。
测试这些故障点
- 请求到达前断网。
- 事务提交后、响应返回前断网。
- 两个相同请求同时到达。
- 同 Key 使用不同 Payload。
- 服务重启后再次请求。
- 下游成功、本地记录暂时失败。
幂等不是一个中间件注解,而是业务唯一性、事务边界和恢复状态共同构成的协议。

评论
0 条讨论