4. 重试机制设计

支付失败并不等于支付终止。真正决定资金安全与用户体验的,是系统在失败之后如何判断“该不该重试、重试几次、隔多久重试、以什么节奏重试,以及如何保证重试不会造成重复扣款”。本节围绕重试机制设计的五个核心要素展开:触发条件、重试次数、重试间隔、退避策略与幂等性保障。

一、核心原理:重试不是“再来一次”,而是受控的状态推进

支付重试的本质,是在不确定的失败结果中,寻找一个可确认的终态。失败可能来自网络超时、渠道限流、银行系统维护、账户余额不足、风控拦截等。不同失败原因的可重试性完全不同。因此,重试机制首先要解决的是失败分类,而不是盲目增加重试次数。

从资金视角看,重试必须同时满足两个约束:一是可恢复性,即该失败在后续时间窗口内有可能成功;二是不重复性,即同一笔支付意图不能被多次扣款。前者决定重试是否有意义,后者决定重试是否安全。幂等性保障正是连接这两者的桥梁。

核心公式:安全重试 = 可重试失败识别 + 受控重试节奏 + 全局幂等键 + 终态收敛判断。

二、重试触发条件:先分类,再决定是否重试

触发条件的设计原则是:系统类失败可重试,业务类失败不重试,不确定类失败需查询后重试。以下清单可作为支付网关的判定依据:

失败类型典型场景是否重试处理策略
网络超时连接渠道超时、读超时是先查询原单状态,再决定重试
渠道限流返回 429、频率超限是指数退避后重试
系统维护渠道返回维护中是延长间隔,限制次数
余额不足账户余额不够否直接失败,通知用户
风控拦截交易被风控拒绝否转人工或换支付方式
参数错误金额、币种、商户号错误否修复参数后重新发起
状态未知渠道未返回明确结果查询后定先查单,确认未成功再重试

其中“状态未知”最危险。若未查询就重试,可能造成重复支付。因此,触发条件中必须包含一条硬规则:任何超时或未知状态,必须先执行一次主动查询,确认原交易未成功后,才允许进入重试队列。

三、重试次数、间隔与退避策略

重试次数不是越多越好。次数过多会放大渠道压力,延长资金占用时间,并增加重复扣款风险。通常建议:同步重试不超过 2 次,异步重试总次数控制在 3~5 次,具体取决于渠道特性和业务容忍度。

重试间隔决定系统恢复能力。固定间隔简单但容易在渠道恢复瞬间形成脉冲流量;指数退避则更符合分布式系统的最佳实践。常见退避策略包括:

  • 固定间隔:每次间隔相同,如 5s、5s、5s,适合内部系统短时抖动。
  • 线性退避:间隔线性增长,如 5s、10s、15s,适合可预测的恢复过程。
  • 指数退避:间隔按倍数增长,如 1s、2s、4s、8s,适合渠道限流与网络拥塞。
  • 指数退避 + 抖动:在指数基础上加入随机抖动,避免大量请求同时重试。
重试计划示例(指数退避 + 抖动):
第1次:1s  + random(0, 500ms)
第2次:2s  + random(0, 500ms)
第3次:4s  + random(0, 500ms)
第4次:8s  + random(0, 500ms)
第5次:16s + random(0, 500ms)
最大重试次数:5
超过后进入人工复核或资金退回流程

实践建议:退避上限应设置封顶值,例如单次间隔不超过 30s;总重试窗口不宜超过 30 分钟,否则用户体验和资金时效都会恶化。

四、幂等性保障:重试安全的核心防线

幂等性保障的目标是:同一笔支付意图,无论被提交多少次,最终只产生一次扣款。实现方式通常包括:

  1. 全局幂等键:由商户订单号 + 支付渠道 + 业务类型生成唯一键,贯穿整个重试生命周期。
  2. 状态机约束:支付单只能从“待支付”进入“支付中”,再进入“成功/失败”,禁止从终态回退。
  3. 渠道幂等参数:调用渠道时携带商户请求号,渠道侧据此去重。
  4. 查询前置:每次重试前先查询原单状态,已成功则直接收敛,不再发起新请求。
  5. 分布式锁:同一幂等键在同一时刻只允许一个重试任务执行。
幂等键生成示例:
idempotency_key = sha256(merchant_id + order_no + channel + "PAY")

重试前检查逻辑:
1. 查询本地支付单状态
2. 若为 SUCCESS,直接返回成功
3. 若为 FAILED 且不可重试,直接返回失败
4. 若为 PROCESSING,调用渠道查单
5. 确认未成功后,加锁并进入重试

五、案例与复盘:一次超时重试引发的重复扣款

某电商平台在大促期间遇到渠道超时,系统未查询原单状态便直接重试,导致同一用户被扣款两次。复盘发现三个问题:一是触发条件未区分“超时”与“明确失败”;二是重试前缺少查单步骤;三是渠道请求号未复用,渠道侧无法去重。

修复方案包括:所有超时失败先查单;重试请求复用同一幂等键;增加重试次数上限与退避抖动;对超过重试窗口的订单自动进入资金退回流程。上线后,重复扣款投诉下降为零。

常见误区:把“重试”当作“重新发起一笔新支付”。正确做法是:重试是同一支付意图的再次推进,必须复用幂等键、复用渠道请求号,并在每次重试前确认原单状态。

支付重试机制设计:五要素协同 触发条件 分类失败原因 重试次数 同步2次 / 异步5次 重试间隔 固定 / 线性 / 指数 退避策略 指数 + 抖动 幂等性保障 幂等键 + 状态机 + 渠道请求号 + 查单前置 不可重试失败 余额不足 / 风控 / 参数错误 可重试失败 超时 / 限流 / 维护 终态收敛 成功 / 失败 / 资金退回 重试前必须查单,确认原交易未成功;重试中复用幂等键;重试后收敛到唯一终态。

总结来看,重试机制设计的关键不在于“重试”本身,而在于用触发条件筛选可恢复失败,用次数与间隔控制节奏,用退避策略平滑压力,用幂等性保障资金安全。只有这五者协同,重试才能成为支付系统稳定性的助力,而不是重复扣款的隐患。