跳转至

经典案例:分布式支付与对账清算系统 (Payment & Reconciliation - 类似 支付宝 与 Stripe)

支付系统(Payment System)是所有互联网商业变现的最后一道关卡,也是整个技术架构中对数据一致性、可靠性与安全性要求最严苛的“金融级”系统。

在其他系统中,丢失一条点赞记录或少统计一次页面浏览,通常影响不大;但在支付系统中,多扣一分钱会引发用户维权与监管处罚,少扣一分钱会导致平台巨额资产流失。

初学者最困惑的是:用户在微信里扣了钱,但如果此时突然手机断网或服务器崩溃,电商平台怎么知道用户到底付没付款?每天数以亿计的流水,系统如何确保“账目一分不差”?


1. 初学者视角:为什么不能用普通的同步接口做支付?

在很多初学者的直觉中,调用支付接口似乎是一个简单的 HTTP POST:

graph LR
    subgraph "朴素同步调用的灾难"
    App[客户端] --> PaySvc[电商支付服务]
    PaySvc -->|HTTP POST 发起扣款| Bank[第三方银行 / 微信 / 支付宝]
    Bank -.->|银行已成功扣款, 但返回时发生网络超时!| PaySvc
    PaySvc -->|因超时给用户报错: 支付失败| App
    NoteNode["致命事故: 用户银行卡扣了 1000 元<br>但系统界面却显示 '支付失败', 且没有生成订单!"]
    end
  • 分布式三态问题:在分布式网络通信中,一次请求不仅有“成功”和“失败”,还存在最棘手的“超时(未知状态)”!
  • 外部银行不可控:银行或第三方支付渠道的处理耗时可能长达数秒,无法直接纳入本地单机事务管理。

2. 需求分析与系统范围 (Requirements & Scope)

2.1 功能性需求

  1. 多渠道支付接入:统一聚合支付宝、微信支付、Apple Pay、信用卡(Stripe)等主流支付渠道;
  2. 异步回调与状态同步:可靠接收第三方支付渠道的结果通知;
  3. 退款与原路返还:支持全额与部分退款,保证资金原路退回;
  4. 每日清算对账 (Reconciliation):每日自动下载银行对账单,实现账目核对与差错自动处理。

2.2 非功能性需求

  1. 金融级绝对一致性 (Zero Data Loss):不漏单、不重扣、账目绝对吻合;
  2. 极高幂等性 (Idempotency):无论网络如何重试,绝不能产生二次重复扣款;
  3. 高可用性 (99.999%):核心支付链路具备多机房容灾与快速故障转移能力。

3. 核心交易状态机与“双重确认”机制

为了彻底解决网络超时导致的“未知状态”,支付系统内部必须维护严格的支付状态机:

stateDiagram-v2
    [*] --> 待支付_INIT
    待支付_INIT --> 支付中_PENDING: 用户跳转至第三方收银台
    支付中_PENDING --> 支付成功_SUCCESS: 收到渠道支付成功确认
    支付中_PENDING --> 支付失败_FAILED: 用户取消或余额不足
    支付中_PENDING --> 超时关闭_CLOSED: 超过 30 分钟未付款
    支付成功_SUCCESS --> 退款中_REFUNDING: 发起退款
    退款中_REFUNDING --> 已退款_REFUNDED: 退款成功到账

状态推进的“双重确认”保障:

为了防止第三方支付渠道的异步通知丢失,系统采用“被动接收 Webhook + 主动定时轮询”双保险:

sequenceDiagram
    participant User as 用户手机
    participant PaySvc as 支付网关服务
    participant Channel as 第三方支付渠道 (微信/支付宝)
    participant Worker as 主动轮询补偿定时任务

    User->>Channel: 1. 在微信/支付宝 App 内完成密码输入并扣款
    par 被动接收 (主流通路)
        Channel-->>PaySvc: 2a. 发送异步通知 Webhook (带签名)
        PaySvc-->>Channel: 回复 SUCCESS 确认已接收
        PaySvc->>PaySvc: 更新状态为 SUCCESS, 触发下游发货
    and 主动补偿 (兜底通路)
        Note over Worker: 若 30 秒内未收到 Webhook 通知
        Worker->>Channel: 2b. 主动调用渠道查询接口: 该单支付了吗?
        Channel-->>Worker: 返回已支付成功
        Worker->>PaySvc: 推进状态为 SUCCESS, 彻底杜绝掉单!
    end

4. 分布式事务在支付中的最佳实践:本地消息表模式

当用户支付成功后,系统需要同时执行两个动作: 1. 更新本地支付记录状态为 SUCCESS; 2. 通知下游订单服务“订单已付款,可以发货”。

为什么不能用微服务常规的分布式事务(如 2PC / XA)? 因为 2PC 性能极其低下且可能发生长时间资源死锁。工业界支付系统首选本地消息表模式 (Local Message Table / Outbox Pattern):

graph TD
    subgraph "单个 MySQL 本地事务 (保证强一致)"
    T1["1. UPDATE payment SET status = 'SUCCESS'"]
    T2["2. INSERT INTO outbox_message (biz_id, payload, status='NEW')"]
    T1 --- T2
    end

    subgraph "异步可靠投递 Worker"
    T2 --> Worker[后台轮询消息 Worker]
    Worker --> MQ[(消息队列 Kafka / RocketMQ)]
    MQ --> OrderSvc[订单发货服务]
    OrderSvc -->|处理成功 ACK| Worker
    Worker -->|更新消息状态为 'SENT'| T2
    end
  • 将支付状态更新与消息记录放在同一个本地数据库事务中提交,要么一起成功,要么一起失败;
  • 后台独立 Worker 保证消息至少一次(At-Least-Once)投递到消息队列,即使宕机重启也不会遗漏任何通知!

5. 金融级生命线:对账与差错处理系统 (Reconciliation)

在金融领域有一句名言:“没有不发生故障的系统,真正的安全感来自对账系统”。

5.1 什么是三方对账?

每天凌晨(如 02:00),第三方渠道(微信、支付宝、银联)会生成前一天的结算对账文件(通常是加密的 CSV 文件)。系统启动批处理任务进行三方核对:

graph TD
    A[(电商平台订单表)] <-->|1. 内部对账| B[(内部支付流水表)]
    B <-->|2. 外部对账| C[(银行/微信官方结算对账单)]

5.2 常见对账差错类型与自动化平账策略

差错现象 产生原因 自动化处理策略 (平账)
外部有单,内部为 PENDING 用户在银行扣了钱,但渠道 Webhook 丢失且主动轮询失败(极罕见但真实存在)。 自动补单:以银行对账单为准,系统自动推进内部支付状态为 SUCCESS,并通知下游补发货物。
内部为 SUCCESS,外部无单 支付网关系统 Bug 或伪造回调。 高危告警:触发紧急报警,冻结该笔交易并由风控人工核查。
两边都有单,但金额不符 如内部记录 100 元,银行对账单为 99 元(汇率换算尾差或优惠券抵扣差错)。 差额流水调整:记录“长款”或“短款”差错单,财务记账并人工审批调账。