支付回调与结算峰值

返回 高并发

秒杀、12306、红包活动结束后,支付渠道会集中回调同一批订单。回调具有重复投递、乱序、突发特征,设计重点是幂等、快速 ACK、异步推进状态机


典型问题

问题后果
微信/支付宝重复通知重复发货、重复加余额
回调处理慢渠道超时重试 → 流量放大
回调与查单并发订单状态竞态
结算、分账、开票堆在同一事务DB 锁、长事务

目标

  • 对渠道:尽快返回 success(通常 < 1s)
  • 对业务:恰好一次推进订单(幂等键)
  • 结算 / 分账:异步,可最终一致

推荐流程

支付渠道 POST 回调
  ▼
网关:验签、解析 out_trade_no
  ▼
幂等层:INSERT notify_log (uniq: channel + notify_id) 或 Redis SETNX
  ▼
若已处理 → 直接 200 success
  ▼
否则:更新订单为「已支付」条件 UPDATE(status=待支付)
  ▼
发 MQ:order.paid → 库存确认 / 发货 / 积分 / 分账
  ▼
返回 200(业务失败也需谨慎:渠道重试策略不同)

核心解法

1. 幂等键选择

来源幂等键
渠道通知notify_id / transaction_id
业务order_id + pay_channel
-- 示例:仅当待支付可推进
UPDATE orders SET status='PAID', paid_at=NOW()
WHERE order_id=? AND status='PENDING_PAY';
-- affected_rows=0 → 已处理或非法,仍记录日志

2. 先落库再 ACK(或 Redis 去重 + 异步)

  • 同步路径:验签 + 幂等表 + 订单状态 CAS(尽量无跨表大事务)
  • 重逻辑:优惠券核销、分账、物流 — 全部走 MQ 消费者,可重试

3. 查单与回调统一入口

用户主动「我已支付」查单与渠道回调应走同一状态机服务,避免两套逻辑。

handlePaymentSuccess(orderId, source=callback|query, idempotentKey)

4. 结算峰值削峰

手段说明
按商户/日期分区消费避免单队列打满
批量结算任务T+1 汇总而非每笔实时写多表
对账任务渠道账单 vs 本地流水,修补差异

5. 超时关单与支付竞态

用户支付成功同时定时任务关单:

  • 关单:UPDATE ... WHERE status=PENDING AND expire_at&lt;NOW()
  • 支付成功:仅 PENDING 可转 PAID
  • 若已 CLOSED 且收到支付 → 走退款/异常单,不要静默丢钱

与秒杀 / 12306 的衔接

阶段要点
下单占库存已扣 Redis/DB 库存
待支付短 TTL 订单 + 延迟关单 MQ
支付回调本页:幂等确认
关单 / 退款回滚库存(与 秒杀与抢购 一致)

检查清单

  • 是否用渠道 notify_id 做唯一约束?
  • 回调是否轻量快速 200?
  • 发货/分账是否异步?
  • 查单与回调是否同一状态机?
  • 关单与支付成功是否有竞态处理与退款路径?
  • 是否有日终对账?

相关