经典案例:分布式支付与对账清算系统 (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 功能性需求¶
- 多渠道支付接入:统一聚合支付宝、微信支付、Apple Pay、信用卡(Stripe)等主流支付渠道;
- 异步回调与状态同步:可靠接收第三方支付渠道的结果通知;
- 退款与原路返还:支持全额与部分退款,保证资金原路退回;
- 每日清算对账 (Reconciliation):每日自动下载银行对账单,实现账目核对与差错自动处理。
2.2 非功能性需求¶
- 金融级绝对一致性 (Zero Data Loss):不漏单、不重扣、账目绝对吻合;
- 极高幂等性 (Idempotency):无论网络如何重试,绝不能产生二次重复扣款;
- 高可用性 (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 元(汇率换算尾差或优惠券抵扣差错)。 | 差额流水调整:记录“长款”或“短款”差错单,财务记账并人工审批调账。 |