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 分钟,否则用户体验和资金时效都会恶化。
四、幂等性保障:重试安全的核心防线
幂等性保障的目标是:同一笔支付意图,无论被提交多少次,最终只产生一次扣款。实现方式通常包括:
- 全局幂等键:由商户订单号 + 支付渠道 + 业务类型生成唯一键,贯穿整个重试生命周期。
- 状态机约束:支付单只能从“待支付”进入“支付中”,再进入“成功/失败”,禁止从终态回退。
- 渠道幂等参数:调用渠道时携带商户请求号,渠道侧据此去重。
- 查询前置:每次重试前先查询原单状态,已成功则直接收敛,不再发起新请求。
- 分布式锁:同一幂等键在同一时刻只允许一个重试任务执行。
幂等键生成示例:
idempotency_key = sha256(merchant_id + order_no + channel + "PAY")
重试前检查逻辑:
1. 查询本地支付单状态
2. 若为 SUCCESS,直接返回成功
3. 若为 FAILED 且不可重试,直接返回失败
4. 若为 PROCESSING,调用渠道查单
5. 确认未成功后,加锁并进入重试
五、案例与复盘:一次超时重试引发的重复扣款
某电商平台在大促期间遇到渠道超时,系统未查询原单状态便直接重试,导致同一用户被扣款两次。复盘发现三个问题:一是触发条件未区分“超时”与“明确失败”;二是重试前缺少查单步骤;三是渠道请求号未复用,渠道侧无法去重。
修复方案包括:所有超时失败先查单;重试请求复用同一幂等键;增加重试次数上限与退避抖动;对超过重试窗口的订单自动进入资金退回流程。上线后,重复扣款投诉下降为零。
常见误区:把“重试”当作“重新发起一笔新支付”。正确做法是:重试是同一支付意图的再次推进,必须复用幂等键、复用渠道请求号,并在每次重试前确认原单状态。
总结来看,重试机制设计的关键不在于“重试”本身,而在于用触发条件筛选可恢复失败,用次数与间隔控制节奏,用退避策略平滑压力,用幂等性保障资金安全。只有这五者协同,重试才能成为支付系统稳定性的助力,而不是重复扣款的隐患。