Skip to content

幂等

自测题

完成以下 3 道题目,检验你的学习成果

问题 1

支付回调为什么必须做幂等?

问题 2

以下哪个最适合作为幂等键?

问题 3

消息重复消费的测试,主要验证什么?

基础入门

幂等(Idempotency)是分布式系统设计中的重要概念,尤其在支付、订单、消息队列等高风险业务场景中必不可少。

它的核心含义是:同一个请求重复执行多次,系统状态和返回结果保持一致或可控。

换句话说,幂等保证重复操作不会产生额外的副作用。

举个例子:支付回调接口收到同一笔支付的多次回调通知,幂等设计能保证只处理一次——数据库只有一条支付记录、订单只发货一次、用户只收到一次通知。

这就是幂等的价值:让系统能够安全地处理重复请求。

幂等的实现通常依赖三个核心要素:幂等键(唯一标识请求,如支付流水号)、状态机(判断订单是否可推进)、重试窗口(控制何时可安全重试)。面试时能讲清这三者的配合关系,能体现对幂等设计的深入理解。

为什么重要

  • 防止资损:支付、订单等高风险场景,重复处理会导致重复扣款、重复发货,直接造成资金损失。
  • 应对网络不确定性:网络超时、服务端处理慢会导致请求重试,幂等让重试变得安全。
  • 面试高频考点:「支付回调为什么做幂等」「你们用什么做幂等键」是测试开发面试的经典问题。
  • 分布式系统标配:微服务架构下服务间调用频繁,幂等是保证系统稳定性的基础能力。
  • 消息队列必备:MQ 的 at-least-once 投递保证需要消费端幂等来配合,否则会导致重复消费。

前置知识

  • HTTP 请求响应:理解请求重试机制,知道网络超时可能导致重复请求。
  • 数据库基础:理解唯一索引、事务,知道如何用数据库约束保证数据不重复。
  • Redis 基础:了解 Redis 分布式锁的基本概念,知道锁用于防并发重复。
  • 业务流程理解:了解支付、订单等基本业务流程,知道什么是「重复扣款」「重复发货」。
  • 状态机概念:理解状态流转(如订单状态从「待支付」到「已支付」只能推进一次)。

学习路径

  • 第一阶段:理解概念。学习幂等的定义、目的、应用场景,理解为什么需要幂等设计。
  • 第二阶段:幂等键设计。学习如何选择幂等键(支付流水号、订单号、requestId),理解幂等键的唯一性和稳定性原则。
  • 第三阶段:数据库实现。学习用唯一索引保证幂等键不重复、用状态机判断是否可推进、用事务保证原子性。
  • 第四阶段:Redis 实现。学习用 Redis 分布式锁防并发重复、用 Redis 缓存请求状态避免重复处理。
  • 第五阶段:测试设计。学习如何设计幂等测试用例,覆盖正常重试、并发重复、超时重放等场景。
  • 第六阶段:进阶实践。学习幂等设计与重试机制、熔断机制、降级机制的配合,理解完整的容错体系。

实操案例:幂等键设计实践

场景:某支付系统需要设计幂等能力,防止支付回调重复处理。

解决方案:幂等键 + 唯一索引 + 状态机

第一步:选择幂等键 选择支付流水号作为幂等键,原因:

  • 业务唯一性:支付流水号是支付系统生成的全局唯一标识,每笔支付只有一个
  • 跨系统一致性:支付网关、商户系统、银行系统都用同一个流水号追踪交易
  • 稳定性:流水号生成规则固定,不会因时间、环境变化而改变

第二步:数据库唯一索引 在订单表的支付流水号字段上创建唯一索引:

ALTER TABLE orders ADD UNIQUE INDEX idx_payment_no (payment_no);

插入支付记录时,如果同一 payment_no 已存在,数据库会抛出唯一约束异常,天然防重复。

第三步:状态机判断 处理支付回调前先检查订单状态:

  • 如果订单状态是「已支付」,直接返回成功,不重复处理
  • 如果订单状态是「待支付」,正常处理支付逻辑
  • 如果订单状态是「已关闭」,返回错误(订单已取消) 状态机确保订单只能从「待支付」推进到「已支付」一次。

面试表达要点:强调幂等键选择原则(唯一性、稳定性、跨系统一致性),数据库唯一索引是最可靠的幂等保障,状态机确保业务状态正确流转。三者配合才能实现完整的幂等设计。

实操案例:并发幂等设计

场景:同一支付流水号的多个回调请求并发到达,需要保证只处理一次。

解决方案:Redis 分布式锁 + 幂等检查

第一步:获取分布式锁 使用支付流水号作为锁的 key,在处理前先获取锁:

lock_key = f"payment:lock:{payment_no}"
if not redis_client.set(lock_key, "1", nx=True, ex=30):
# 获取锁失败,说明有其他请求正在处理,等待或直接返回
return {"status": "processing"}

nx=True 保证只有一个请求能获取到锁,ex=30 设置 30 秒过期防止死锁。

第二步:幂等检查 获取锁后再次检查幂等状态(双重检查):

order = db.query("SELECT * FROM orders WHERE payment_no = %s", payment_no)
if order and order.status == "paid":
return {"status": "already_paid"}

防止在获取锁之前已有请求处理完成。

第三步:处理业务逻辑并释放锁 使用 try-finally 确保锁一定被释放:

try:
# 处理支付逻辑:写订单、扣库存、发消息
process_payment(payment_no)
finally:
# 删除锁,Lua 脚本保证原子性
redis_client.delete(lock_key)

面试表达要点:强调并发场景下单一数据库唯一索引不够,需要分布式锁防并发重复。双重检查(获取锁前 + 获取锁后)确保万无一失。锁过期时间设置要合理,避免处理未完成锁已过期。

常见误区

误区一:把接口返回一样当成幂等

接口返回相同不代表幂等。

真正的幂等要验证数据库状态、外部系统调用是否重复。

例如支付回调,即使接口返回相同,但若数据库写入重复订单、外部通知发送多次,就不是幂等。

面试时要强调:幂等验证要覆盖数据库、消息队列、外部通知等多层副作用。

误区二:只在客户端做去重

客户端去重无法保证服务端幂等,因为请求可能从不同客户端发起。服务端必须设计幂等键(如订单号、requestId),配合数据库唯一索引或 Redis 锁来保证重复请求不会产生副作用。

面试时要指出:幂等是服务端的责任,不能依赖客户端。

误区三:没有讲清幂等键、状态机和重试窗口

幂等键是防重的核心字段(如支付流水号),状态机用于判断订单是否可推进,重试窗口控制何时可安全重试。面试回答要把这三点串起来:幂等键唯一标识请求,状态机确保状态正确流转,重试窗口防止过期请求被处理。三者配合才能实现完整的幂等设计。

误区四:忽略并发场景

并发请求会导致「检查时存在、写入时重复」的问题。

正确做法是用分布式锁(如 Redis 锁)或数据库行锁(SELECT FOR UPDATE)在检查前加锁,确保同一幂等键的请求串行处理。

面试时要强调:并发幂等是必考场景,不能只测串行情况。

误区五:幂等设计过度依赖应用层

应用层逻辑可能被绕过,最可靠的是数据库层约束。

正确做法是:应用层做幂等检查提升性能,数据库层用唯一索引兜底保证数据不重复。

面试时要强调:数据库唯一索引是幂等的最后一道防线。

面试问答

支付回调为什么一定要做幂等?

支付回调必须做幂等,原因有三点:

第一,网络不确定性导致重复回调。支付网关发送回调后,如果没收到确认响应(网络超时、服务端处理慢),会自动重试发送,同一笔支付可能收到多次回调。

第二,重复处理会造成严重业务后果。\n\n如果回调不做幂等,会导致重复扣款、重复发货、重复通知用户,引发资损和用户投诉。

第三,幂等是金融系统的底线要求。支付是高风险场景,监管和业务方都要求「同一笔交易只能成功一次」。

具体实现上,用幂等键(支付流水号)配合状态机判断订单是否已处理:如果订单状态是「已支付」,则直接返回成功不重复处理。\n\n如果订单状态是「待支付」,则正常处理支付逻辑。

你们项目里用什么做幂等键?

我们项目用支付流水号作为幂等键,这是最常见也是最稳定的选择。幂等键的选择原则有三点:

第一,业务唯一性。支付流水号是支付系统生成的全局唯一标识,每笔支付只有一个。

第二,跨系统一致性。支付流水号会传递给商户系统和银行系统,三方都用同一个流水号追踪交易。

第三,稳定性。支付流水号生成规则固定,不会因为时间、环境变化而改变。

具体实现上,我们用数据库唯一索引保证幂等键不重复写入,同时用 Redis 分布式锁在处理前先锁定幂等键,避免并发处理同一请求。

如果消息重复消费,你怎么设计测试用例?

消息重复消费的测试设计要覆盖三种场景:

第一,正常重试场景。模拟消息消费失败后 MQ 自动重试,验证重试消费时幂等逻辑生效,数据不重复写入、状态不重复变更。

第二,并发重复消费场景。构造多个消费者同时消费同一条消息,验证并发消费时幂等逻辑生效,数据库只有一条记录。

第三,超时后重新消费场景。模拟消费超时后消息重新投递,验证重新投递时幂等逻辑生效。具体测试方法上,我会用 MQ 管理工具手动重发消息,或在测试代码中用同一消息 ID 多次调用消费逻辑,然后验证数据库记录数量、状态一致性、外部调用次数。

幂等和重试机制是什么关系?

重试和幂等是互补关系。重试是调用方的容错策略:当请求失败(超时、网络错误)时自动重试,提高请求成功率。幂等是被调用方的安全保障:保证重复请求不会产生副作用。两者配合才能实现可靠的分布式调用:调用方放心重试,被调用方安全处理重复请求。

面试时可以举例:支付网关会重试发送回调,我们服务端做幂等处理,这样即使网络不好导致多次回调,也不会重复扣款。

自测题

完成以下 3 道题目,检验你的学习成果

问题 1

支付回调为什么必须做幂等?

问题 2

以下哪个最适合作为幂等键?

问题 3

消息重复消费的测试,主要验证什么?