4. 幂等性核心概念

在资金扣款场景里,幂等不是一个“可选优化”,而是系统正确性的底线。用户点一次“支付”,网络抖动、超时重试、网关重发、消息补偿都可能让同一笔业务请求到达后端多次。如果后端没有幂等控制,就会出现重复扣款、重复入账、重复发券等严重资损问题。本节先把幂等性的核心概念讲清楚:什么是幂等、幂等键如何设计、唯一标识如何落地、重复请求从哪里来,以及幂等边界应该划在哪里。

一、幂等定义:同一业务意图,多次执行结果一致

幂等(Idempotence)在数学上指函数 f(f(x)) = f(x),在工程上我们更关心业务语义:同一个业务请求,无论被处理一次还是多次,对系统状态产生的最终影响相同。注意这里的关键词是“业务意图”和“最终影响”,而不是“接口被调用了几次”。

举例说明:

  • 扣款 100 元,第一次成功扣减余额,第二次到达时如果再次扣减,余额就少了 200 元,这就不幂等。
  • 如果第二次到达时系统识别出“这笔扣款已经处理过”,直接返回第一次的结果,余额仍只减少 100 元,这就是幂等。
  • 查询类接口天然幂等,因为它不改变状态;但“创建订单”“扣减库存”“发起转账”这类写操作必须显式设计幂等。

核心判断标准:重复请求到达时,系统状态是否与第一次执行后完全一致?如果一致,就是幂等;如果产生了额外副作用,就不幂等。

二、幂等键与唯一标识:把“同一笔业务”识别出来

要实现幂等,系统必须有能力判断“这两个请求是不是同一笔业务”。这个判断依据就是幂等键(Idempotency Key),通常也叫唯一标识、请求号、业务流水号。它由调用方生成,随请求一起传递,服务端用它做去重。

幂等键的设计有几个要点:

  1. 全局唯一:同一业务场景下不能重复,通常用 UUID、雪花 ID 或“业务类型 + 业务单号”组合。
  2. 由调用方生成:服务端生成无法区分“重试”和“新请求”,调用方最清楚业务意图。
  3. 与业务语义绑定:例如“订单支付”场景,幂等键可以是 pay:orderId:requestId,而不是随机字符串。
  4. 可追溯:幂等键要能写入日志和数据库,便于对账和排障。

下面是一个典型的幂等键生成与传递示例:

// 调用方生成幂等键
String idempotentKey = "PAY_" + orderId + "_" + UUID.randomUUID();

// 通过 HTTP Header 传递
POST /api/pay/deduct
Header: X-Idempotent-Key: PAY_20240520_8891_a3f2c1
Body: { "orderId": "8891", "amount": 10000, "userId": "U10086" }

// 服务端接收后先查幂等表
SELECT status, result FROM idempotent_record
WHERE idempotent_key = 'PAY_20240520_8891_a3f2c1';

服务端一般会建立一张幂等记录表,字段包括幂等键、业务状态、返回结果、创建时间、过期时间。处理流程是:先插入幂等键(利用唯一索引防重),插入成功则执行业务;插入冲突则说明是重复请求,直接返回已有结果或提示处理中。

三、重复请求来源:它们比你想的更常见

很多开发者以为“用户只点一次就不会重复”,实际上重复请求的来源非常多样。理解来源,才能设计出覆盖全面的幂等方案。

重复来源 典型场景 特征
用户重复点击 支付按钮未置灰,用户连点多次 短时间高频,参数完全相同
客户端超时重试 网络慢,SDK 自动重发 间隔固定,幂等键相同
网关/负载均衡重发 上游超时后转发到另一台机器 跨节点,需全局去重
消息队列重复投递 MQ 至少一次语义,消费失败重投 可能延迟到达,需长期幂等
定时补偿任务 对账发现“未完成”主动补单 可能重复触发已成功业务
人工运维操作 运维手动重放请求 难以预测,需强幂等兜底

注意:“至少一次”投递语义在分布式系统中是常态。任何跨网络、跨进程的调用,都必须假设请求可能重复到达。幂等设计不是防用户,而是防整个链路的不确定性。

四、幂等边界:在哪里做幂等,做到什么程度

幂等边界指的是:幂等控制应该在哪一层生效,覆盖哪些操作,保留多久。边界划得太窄,重复请求会穿透;划得太宽,会带来性能和存储成本。

常见的边界划分方式:

  • 接口层幂等:在 Controller 或网关层拦截重复请求,适合短时间防重,但无法覆盖 MQ 重投。
  • 服务层幂等:在业务服务内用幂等键 + 唯一索引控制,是最常用的方式,能覆盖大多数场景。
  • 数据库层幂等:利用唯一约束、乐观锁、状态机流转,作为最后一道防线。
  • 业务状态机幂等:例如订单只能从“待支付”流转到“已支付”,重复请求因状态不满足而被拒绝。

幂等记录的保留时间也需要权衡:太短,延迟重投可能穿透;太长,存储和查询成本高。资金类业务通常保留 7 天到 30 天,甚至永久保留关键流水。

实践建议:资金扣款场景建议采用“幂等键 + 唯一索引 + 状态机”三重保障。幂等键负责识别同一笔业务,唯一索引负责并发防重,状态机负责业务语义上的合法流转。三者结合,才能在高并发下既保证性能又保证正确性。

重复请求来源与幂等边界 重复请求来源 用户重复点击 客户端超时重试 网关/负载均衡重发 消息队列重复投递 定时补偿任务 人工运维重放 幂等键 Idempotency Key 全局唯一 调用方生成 与业务语义绑定 可追溯 幂等边界层次 接口层 短时间防重,网关拦截 服务层 幂等键 + 唯一索引 数据库层 唯一约束、乐观锁 业务状态机 合法状态流转校验 保留时间 资金类 7~30 天或永久 携带 校验

总结来看,幂等性的本质是“用唯一标识识别同一笔业务,用边界控制保证重复请求不产生额外副作用”。幂等键是识别工具,唯一标识是落地手段,重复请求来源决定了方案的覆盖面,幂等边界决定了控制的位置和强度。把这四个概念理解透,后续的并发控制、状态机、分布式锁等实战手段才有稳固的落脚点。