幂等性
Idempotency
- 最后更新
- 2026年7月16日
- 概念连接
- 6
一句心智模型
同一个意图可以被重复送达;系统只让它产生一次业务效果,后续重试复用第一次的结果。
视觉记忆卡
用一张图记住这个 Pattern。
把解释压缩成一个视觉模型,下一次在不同例子中遇到它时,也能更快识别出来。

01 · 结果未知实验室
扣款已经成功,响应却消失了。
客户端没有成功或失败的证据。分别在没有稳定身份和使用幂等键时推进重试,观察同一个业务意图怎样走向两个不同结果。
一笔 $42 支付等待发送。
尚未收到请求。
没有支付记录。
02 · Key 契约
只有难处理的情况都有规则,幂等键才真正有用。
选择一种情况。真正的设计工作发生在意图身份、原子所有权、并发和时间边界上。
相同调用者 · 相同 key · 相同参数重试靠业务意图识别,不靠它是第几次到达。
03 · 操作判断
先问:这个操作能否自己收敛?
自然幂等比维护账本更简单。相对变化和外部副作用才需要完整的身份协议。
SET status = PAID自然幂等每次重复都收敛到同一个被明确命名的状态。
DELETE document 42自然幂等响应可以不同,但资源最终始终不存在。
POST create payment需要意图 key如果重试没有共享身份,每次创建都会成为一笔新扣款。
balance = balance + 10非自然幂等每次执行都会再次改变状态。
04 · 概念邻居
不要混淆恢复、检测、交付和业务效果。
它们经常一起出现,但分别回答不同问题。关系类型比缩写本身更值得记住。
什么时候再次尝试,退避节奏是什么?
哪些尝试表达的是同一个业务意图?
怎样检测或压制重复交付?
为了降低丢失,消息是否可能到达多次?
怎样把数据库变化与最终消息发布可靠地连接起来?
即使交付和执行重复,限定范围内的效果能否表现为一次?
幂等性
Idempotency(幂等性)解决的是分布式系统里最棘手的一类失败:不是明确成功或失败,而是 unknown outcome(结果未知)。
客户端发起支付;服务端已经扣款,但响应在网络中消失。客户端只看到超时。如果放弃重试,本该完成的支付可能被误认为失败;如果直接重试,用户又可能被扣两次。
幂等契约把恢复与重复分开:请求可以再次尝试,但同一个业务意图不能再次产生业务效果。
餐厅取餐号
想象你把编号 A-842 的点菜单递给厨房,却没有听见确认,于是又递了一次。
- 没有订单号,厨房可能做两顿饭。
- 有稳定订单号,厨房查到
A-842,只返回原订单状态。 - 真正的新订单必须使用新编号。
- 仍用
A-842却换了菜品,厨房应该拒绝,而不是猜测。
取餐号只是身份。厨房仍然需要可靠账本,而且登记号码与接单之间不能有不安全的缝隙。
数学定义与系统定义
数学里的幂等函数满足:
f(f(x)) = f(x)
软件系统更关心语义:同一请求执行多次,其 intended effect(预期效果) 与执行一次相同。它不要求响应字节完全相同,也不禁止额外的日志、指标和时间戳。
例如,第一次 DELETE /documents/42 返回 204,第二次返回 404。响应不同,但资源最终都处于“不存在”的状态。
两种获得幂等性的方式
让操作自然幂等
状态设定通常会收敛:
SET order.status = "PAID" repeated → still PAID
DELETE document 42 repeated → still absent
PUT /profile { name: "Li" } repeated → same representation
相对变化通常不会:
balance = balance + 10
toggle subscription
send welcome email
create a new charge
一个很实用的判断是:能否把“再变化一次”改写成“让它等于这个状态”?
增加 Idempotency Key(幂等键)
创建支付、预约或云资源不能总被改写成简单赋值。这时客户端为一个业务意图生成稳定 key,并在每次重试中复用。
Idempotency-Key: order-842-payment
POST /payments { amount: 42, currency: "CAD" }
完整协议通常要做到:
- 一个意图一个 key;新意图使用新 key;
- 按调用者、账户和操作限定 key 的作用域;
- 用唯一约束或事务原子占位;
- 绑定请求指纹,参数变化时拒绝;
- 记录处理中、完成或失败状态;
- 向已完成的重复请求重放原结果或语义等价结果;
- 定义并发重复请求看到什么;
- 声明 TTL(Time to Live,存活时间),说明 key 何时可能被当作新请求。
原子缝隙才是真正危险的地方
下面的实现存在竞态:
if key does not exist:
charge_card()
save(key, result)
两个并发请求可能同时看到 key 不存在,于是都扣款;服务也可能在扣款后、保存记录前崩溃,让重试无法与新请求区分。
只要可能,key 占位、业务状态变化与结果记录就应该共享一个原子边界。如果效果跨过数据库、队列、邮件或第三方支付系统,每段边界都要有自己的幂等策略,通常需要状态机、Transactional Outbox(事务发件箱)或消费者 Inbox(收件箱)。
只有一个 header,没有原子状态机,只是装饰,不是保证。
契约必须说明的四种情况
已完成的重复请求
返回第一次记录的结果,或语义等价的当前结果;不要再次执行业务动作。
相同 key,不同参数
必须拒绝,否则服务无法判断这是一次重试,还是一次错误的 key 复用。Stripe 与 Amazon Elastic Compute Cloud(Amazon EC2)都把参数不匹配视为错误。
同时到达的重复请求
第二个请求不能也开始产生效果。它可以等待、收到 in progress,或收到冲突响应;API(Application Programming Interface,应用程序编程接口)契约必须做出选择。
已过期的 key
幂等记录通常不会永久保存。记录清理后,同一个 key 可能再次执行。服务端承诺的保证窗口必须覆盖客户端最长的重试周期。
HTTP:安全不等于幂等
HTTP(Hypertext Transfer Protocol,超文本传输协议)把 safe method(安全方法)与 idempotent method(幂等方法)分开。
| Method | Safe? | 语义上幂等? | 含义 |
|---|---|---|---|
GET | yes | yes | 请求读取,不应要求状态变化。 |
PUT | no | yes | 用给定表示替换目标,重复提交仍然收敛。 |
DELETE | no | yes | 会改变一次状态,但重复删除的预期效果相同。 |
POST | no | 默认 no | 常表示“再创建一个”,需要业务层协议赋予幂等性。 |
所以,一个操作可以改变状态,同时仍然幂等。幂等不等于无害或只读。
看清相邻概念
- Retry(重试)是恢复策略。 它控制 timeout、尝试上限、exponential backoff(指数退避)与 jitter(抖动);幂等性让这些尝试不会重复产生业务效果。
- Deduplication(去重)是检测机制。 它可以帮助实现幂等性,但语义契约不只是丢弃重复。
- At-least-once Delivery(至少一次交付)是交付保证。 它可能重复投递,所以消费者需要幂等处理。
- Exactly-once(恰好一次)是更强、也常被误用的保证。 幂等性不阻止重复投递或执行,只让限定范围内的效果收敛得像发生一次。
- Optimistic Concurrency Control(OCC,乐观并发控制)保护陈旧写入。 OCC 区分互相竞争的不同意图;幂等性识别同一意图的再次送达。
- Transactional Outbox(事务发件箱)关闭跨系统一致性缝隙。 它仍然允许重复发布,所以消费者仍需幂等。
它不能解决什么?
- 不能阻止重试风暴;仍需次数上限、backoff、jitter、限流与熔断。
- 不能自动跨越数据库、队列、邮件和第三方 API。
- 不能阻止两个不同 key 同时争抢同一份库存。
- 不能替 API 决定是否缓存并重放第一次
500。 - 不能创造全局 exactly-once processing(恰好一次处理)。
最后记住五件事
- 幂等性回应的是结果未知:可以重试这次尝试,不能重复这个意图的效果。
- 领域允许时,优先把动作改成自然幂等的状态设定。
- 一个意图一个 key;重试复用它;新意图换新 key。
- 相同 key + 不同参数必须拒绝,并明确并发与过期行为。
- 最危险的 bug 位于副作用与幂等记录之间的非原子缝隙。
自测
- 支付已经提交但响应消失时,什么证据能让重试安全?
- 你的 key 识别业务意图,还是只对 payload 做 hash?
- 相同 key 携带不同金额时会发生什么?
- 两个重复请求同时到达时,谁获得执行权?
- key 的寿命是否覆盖客户端最长的重试周期?
Further reading
概念连接
概念邻居
Retry
恢复策略Retry(重试)
决定何时、怎样再次尝试;幂等性决定再次尝试会不会重复产生业务效果。
Idempotency Key
请求身份机制Idempotency Key(幂等键)
为一个业务意图命名,让系统把每次重试识别为同一次操作。
Deduplication
重复检测机制Deduplication(去重)
检测或压制重复,是实现幂等行为的一种技术机制。
At-least-once Delivery
交付保证At-least-once Delivery(至少一次交付)
消息可能被重复投递,因此消费者需要用幂等处理安全吸收重复。
Transactional Outbox
一致性模式Transactional Outbox(事务发件箱)
消除数据库变更与消息发布之间的不安全缝隙,同时仍要求消费者处理重复。
Exactly-once
更强保证Exactly-once Processing(恰好一次处理)
常被过度宣称的端到端保证;幂等性通常只让效果收敛,并不阻止重复投递或执行。
证据路径
