幂等
自测题
完成以下 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
消息重复消费的测试,主要验证什么?
解析:消息幂等测试核心是验证重复消费时副作用是否可控:数据库记录不重复、状态不重复变更、外部调用只发生一次。