3377 字
17 分钟
幂等键(Idempotency Key)

一、什么是幂等键?#

幂等键是用于识别同一笔业务请求的唯一标识。

它主要解决网络超时、重复提交、自动重试导致的重复执行问题。

例如:用户扣款 100 元,第一次请求可能已经扣款成功,但由于网络超时,调用方没有收到结果。如果直接重试,就可能重复扣款。

因此,系统为这笔业务生成幂等键 PAY001。重试时继续使用 PAY001,服务端识别出这是同一笔业务,就不会再次执行扣款。

核心记忆:同一笔业务,重试时使用同一个幂等键。

二、为什么需要幂等键?#

以支付扣款为例:

  1. 用户发起扣款 100 元。
  2. 服务端调用支付平台,扣款成功。
  3. 返回结果时发生网络超时。
  4. 调用方没有收到结果,发起重试。
  5. 如果服务端再次扣款,就可能造成重复扣款。

注意:网络超时不代表业务执行失败,只代表调用方没有及时收到结果。

幂等键可以帮助服务端识别重复请求,避免重复执行。

三、服务端如何实现幂等?#

1. 数据库唯一约束#

服务端将幂等键保存到数据库,并对幂等键设置唯一约束。

具体体现:建表时给 idempotency_key 字段添加 UNIQUE。

CREATE TABLE payment_requests (
id BIGSERIAL PRIMARY KEY,
idempotency_key VARCHAR(100) NOT NULL UNIQUE,
amount DECIMAL(10, 2) NOT NULL,
status VARCHAR(20) NOT NULL,
result JSONB
);

例如,数据库中已经存在:

幂等键状态金额
PAY001SUCCESS100

如果再次插入 PAY001,数据库就会阻止重复插入。

作用:保证同一个幂等键不能重复登记。

注意:唯一约束只能防止重复登记,不能单独阻止重复扣款,还需要配合业务执行逻辑。

2. 记录业务处理状态#

服务端记录每笔请求的处理状态:

  • PROCESSING:正在处理。
  • SUCCESS:处理成功。
  • FAILED:处理失败。

收到重复请求时,根据状态决定如何处理:

  • 已成功:不再执行扣款,返回已有结果。
  • 正在处理:返回处理中,或查询原请求的进度。
  • 处理失败:根据失败原因判断是否可以安全重试。

作用:让服务端知道这笔业务处理到了哪一步。

3. 处理并发请求#

两个相同请求可能同时到达服务端。

如果两个请求都先检查数据库,发现幂等键不存在,然后同时执行扣款,就可能重复执行。

因此,需要利用数据库唯一约束和原子操作等手段,确保只有一个请求能够取得处理资格。

(1)通过插入记录竞争处理资格#

不是让两个请求先查询、再决定谁执行,而是让它们直接尝试插入同一个幂等键。

INSERT INTO payment_requests
(idempotency_key, amount, status)
VALUES
('PAY001', 100.00, 'PROCESSING')
ON CONFLICT (idempotency_key) DO NOTHING
RETURNING id;

这条 SQL 的含义:

  • INSERT:尝试创建请求记录。
  • ON CONFLICT (idempotency_key) DO NOTHING:如果幂等键已经存在,就不重复插入。
  • RETURNING id:插入成功时返回新记录的 ID;没有插入时不返回记录。

程序根据返回值判断是否取得处理资格:

  • 返回了 ID:本次插入成功,可以继续处理。
  • 没有返回 ID:说明已有相同幂等键的记录,应查询已有状态,不能再次执行扣款。

(2)并发时数据库如何处理?#

假设请求 A 和请求 B 同时执行上述 SQL:

  1. A 尝试插入 PAY001,插入成功。
  2. B 也尝试插入 PAY001。
  3. 如果 A 的事务尚未结束,B 可能等待 A 的结果。
  4. A 提交成功后,B 发现唯一键冲突,不会插入。
  5. 如果 A 回滚,B 则有机会插入成功。

这就是数据库利用唯一约束,在并发情况下决定谁成功创建记录。

(3)事务、原子操作和锁分别是什么?#

机制具体作用
唯一约束防止相同幂等键重复登记
事务保证一组数据库操作要么一起提交,要么一起回滚
原子操作将关键判断和修改合并为一个不可分割的操作
数据库锁控制多个事务对同一条记录的并发修改

例如,使用事务:

BEGIN;
INSERT INTO payment_requests
(idempotency_key, amount, status)
VALUES
('PAY002', 100.00, 'PROCESSING');
-- 在这里执行其他需要一起提交的数据库操作
COMMIT;

如果事务失败并回滚,事务内尚未提交的数据库修改也会回滚。

注意:事务只能保证数据库内部操作的一致性,不能直接保证第三方支付平台也一起成功或回滚。

对于首次创建请求,通常使用“唯一约束 + 原子插入”来竞争处理资格;对于已有记录的状态修改,可以根据业务需要使用原子更新或行级锁。

四、为什么只有幂等键还不够?#

假设服务端执行以下操作:

  1. 调用支付平台。
  2. 支付平台扣款成功。
  3. 服务端准备把数据库状态更新为 SUCCESS。
  4. 服务端突然崩溃。

此时,支付平台已经扣款,但本地数据库可能仍然显示 PROCESSING。

如果服务端重启后直接再次扣款,就可能发生重复扣款。

因此,幂等键必须配合状态管理和业务一致性控制。

第三方支付的幂等与异常处理#

核心问题:

调用第三方支付平台时,可能出现网络超时。超时不代表支付失败,因为平台可能已经扣款成功,只是返回结果时网络中断。

核心原则:支付结果不明确时,先确认真实状态,不要盲目重新扣款。

1. 本地记录请求和处理状态#

调用支付平台之前,先在本地数据库创建支付记录。

例如:

订单号金额状态
ORDER1001100 元PROCESSING

PROCESSING 表示支付流程正在处理,不代表已经扣款成功。

作用: 记录这笔业务的处理进度,方便后续查询、重试和恢复。

2. 调用支付平台时使用其支持的幂等机制#

调用支付平台时,携带同一笔业务的稳定标识,例如商户订单号 ORDER1001。

如果需要重试同一笔支付,就继续使用原来的业务标识,并遵循支付平台的幂等规则。

作用: 让支付平台识别同一笔业务的重复请求,避免重复扣款。

注意:不同支付平台的幂等机制不同,要以实际接口文档为准。有些平台通过商户订单号识别重复业务,不一定提供名为 Idempotency-Key 的字段。

3. 网络超时后,不直接认定失败#

例如,系统请求扣款 100 元,但等待响应时发生超时。

此时可能有两种情况:

  • 平台已经扣款成功,但成功响应没有返回。
  • 平台没有完成扣款,或者仍在处理中。

因此,不能仅凭超时就把订单标记为失败,也不能直接创建新的支付请求。

作用: 避免把“没有收到结果”误判成“业务执行失败”。

4. 查询支付订单状态,确认是否已经扣款#

调用支付平台提供的订单查询接口,查询原来的商户订单号或支付单号。

根据查询结果处理:

  • 支付成功: 准备更新本地状态。
  • 处理中: 稍后再次查询。
  • 明确未支付: 根据平台规则判断是否可以安全重试。
  • 结果仍不明确: 保留待确认状态,继续查询或进入异常处理。

作用: 以支付平台的实际订单状态为依据,而不是猜测支付是否成功。

5. 根据确认结果更新本地状态#

如果确认支付成功,就把本地记录更新为 SUCCESS,并保存支付平台的交易号等信息。

例如:

ORDER1001:PROCESSING → SUCCESS

如果确认失败,也要按照支付平台的状态定义和业务规则更新。

如果本地更新失败,不要重新扣款,而应重新查询平台结果,并安全地重试本地状态更新。

作用: 让本地订单状态尽可能与支付平台的真实结果保持一致。

6. 必要时通过回调、重试和对账修复状态不一致的问题#

支付回调

支付平台主动通知商户支付结果。商户需要验证签名、订单号和金额,再更新本地状态。

回调可能重复发送,因此回调处理也必须具备幂等性。

重试

对暂时失败的查询、通知处理或本地数据库更新等操作,可以按规则重试。

重试时要区分“查询或保存结果”和“重新发起扣款”,不能把所有失败都当成可以重新支付。

对账

定期比对支付平台的交易记录与本地订单。

例如,平台显示支付成功,但本地订单仍然是 PROCESSING,就需要进一步核实并修复本地状态。

作用: 处理回调丢失、服务崩溃、状态更新失败等情况留下的问题。

完整流程#

  1. 本地创建支付记录,状态设为 PROCESSING。
  2. 调用支付平台,使用平台支持的幂等机制。
  3. 如果网络超时,不直接认定失败。
  4. 查询支付订单状态,必要时结合支付回调确认。
  5. 根据确认结果更新本地状态。
  6. 通过重试和定期对账,发现并修复状态不一致的问题。

注意:本地数据库和第三方支付平台通常是两个独立系统,仅靠本地事务不能保证两边同时成功或失败。

第三方支付面试口播#

对于第三方支付,我会先在本地记录订单和处理状态,再调用支付平台,并使用平台支持的幂等机制,避免同一笔业务重复扣款。

如果请求超时,我不会直接认定支付失败,因为平台可能已经扣款成功,只是响应没有返回。我会通过订单查询接口确认结果,同时处理支付平台的回调通知。

确认支付成功后,再更新本地订单状态。如果结果暂时不明确,就保留待确认状态,按规则继续查询,而不是盲目重新扣款。

此外,我会通过重试处理暂时失败的操作,并定期对账,发现和修复本地记录与支付平台实际结果不一致的问题。

记忆口诀:先记录、再支付;超时不判败,查单确认;更新本地,回调重试,对账兜底。

五、幂等键、重试、事务的区别#

概念主要作用
幂等键识别是不是同一笔业务请求
重试机制请求超时或失败时,再次尝试
数据库唯一约束防止同一个键被重复登记
状态管理记录业务当前处理到哪一步
事务保证一组数据库操作的一致性
业务一致性控制处理本地状态与外部业务结果不一致的问题

六、幂等键面试口播答案#

幂等键是用来识别同一笔业务请求的唯一标识,主要解决网络超时、重复提交和重试导致的重复执行问题。

例如,支付扣款时,第一次请求可能已经扣款成功,但由于网络超时,调用方没有收到结果。如果直接重试,就可能重复扣款。

因此,我会为每笔业务生成一个幂等键,重试时保持这个键不变。服务端通过数据库唯一约束、状态记录和并发控制识别重复请求,已经成功的请求直接返回之前的结果。

但仅有本地幂等记录还不够。如果支付平台已经扣款成功,而服务端在保存成功状态之前崩溃,本地状态就可能与真实支付结果不一致。因此,还需要结合支付平台的幂等机制、订单状态查询、回调和对账,保证业务结果正确。

七、记忆口诀#

同键识别、唯一防重、状态判断、并发控制、超时查单。

  • 同键识别:重试时使用同一个幂等键。
  • 唯一防重:数据库唯一约束防止重复登记。
  • 状态判断:区分处理中、成功和失败。
  • 并发控制:避免多个请求同时执行同一笔业务。
  • 超时查单:结果不明确时先确认真实业务状态,不盲目重试。

一句话总结:重试解决“没收到结果怎么办”,幂等解决“重试后会不会重复执行”。

相关文章#

幂等键(Idempotency Key)
https://blog.xqcherry.top/posts/interview/scenarios/idempotency-key/
作者
xqcherry
发布于
2026-10-22
许可协议
CC BY-NC-SA 4.0