第 7 章 高准确性与强一致性系统设计方法论:支付、库存与账务场景
当系统面对支付、库存、账务、钱包、积分等“结果必须正确”的业务时,设计重点不再只是流程能不能跑通,而是事实能不能被严格定义、状态能不能被安全推进、错误能不能被可审计地恢复。
在上一章里,我们讨论了大事务如何通过状态机、Saga 和工作流来处理跨系统流程推进问题。但不是所有大事务都处在同一个风险级别上。
对于营销推送、审批流、供应商同步这类流程,允许一定程度的最终一致性,通常问题不大。
但对于支付、订单金额、库存扣减、账务记账、钱包余额、积分发放这类系统,如果出现重复执行、状态错乱、金额丢失、库存超卖,后果往往不是“用户体验差一点”,而是直接导致资损、合规风险和人工对账成本失控。
所以这一章讨论的不是“所有大事务”,而是其中要求最高的一类:
高准确性与强一致性场景,本质上是在高并发和分布式条件下,想办法让业务事实始终正确、状态始终可解释、异常始终可恢复。
7.1 什么叫高准确性与强一致性场景
先把“高准确性”和“强一致性”拆开理解。
- 高准确性:强调业务结果不能算错、扣错、记错、发错。
- 强一致性:强调关键状态在某个时刻必须具备唯一、明确、不可歧义的事实定义。
这两个概念通常一起出现,但关注点并不完全相同。
例如:
- 支付成功后,账务金额必须一分不差,这是高准确性。
- 同一笔订单不能同时处于“已支付”和“未支付”,这是强一致性。
- 同一件库存不能被两个请求同时成功扣减,这是强一致性。
- 钱包余额更新后不能凭空多出或少掉金额,这是高准确性。
这类系统通常有四个共同特征:
- 错误代价高:一旦出错,常常直接变成资损、投诉或审计问题。
- 状态必须唯一:系统不能长期容忍“到底成功没成功说不清”的状态。
- 重复执行危险:重复扣款、重复扣库存、重复发券都会制造真实副作用。
- 事后必须可追溯:要能回答“谁在什么时间对哪笔事实做了什么动作”。
典型案例包括:
- 支付、退款、清结算、钱包、积分、优惠核销
- 订单金额确认、应收应付计算、税费和手续费入账
- 库存预占、库存确认、库存回补、防超卖
- 财务对账、账本修复、补单补账
- 合约执行、链上写入、不可变日志相关系统
7.2 这类问题为什么不能只靠“最终一致性”口号解决
很多团队在业务初期会说一句话:“先异步化,最终一致就行。”
这句话对很多普通业务是成立的,但对支付、库存、账务这类场景,常常是不够的。
原因是这里存在一个关键差异:
- 普通流程更关心“任务最终有没有完成”
- 强一致流程更关心“中间任何时刻的业务事实能不能被信任”
举几个典型反例:
7.2.1 支付成功但订单仍显示未支付
如果只是延迟几秒展示,问题不大;
但如果系统因此再次发起扣款、重复触发履约,问题就从“延迟一致”变成了“事实错误”。
7.2.2 库存异步扣减导致超卖
如果下单成功只是先记一条消息,稍后再去扣库存,那么在高并发情况下,同一个库存窗口可能已经被多次卖出。
这时后续补偿虽然能把一部分订单取消,但超卖本身已经发生,用户体验和业务承诺都被破坏了。
7.2.3 钱包余额更新依赖多次异步拼接
如果余额不是由账本严格推导,而是通过多个异步任务“慢慢修正”,那么一旦消息乱序、重复消费、部分失败,系统就很难保证余额始终正确。
所以一个很重要的判断是:
在高准确性场景里,不能把所有问题都外包给“最终一致性”;必须先划清哪些环节必须同步确认、哪些环节可以异步收敛。
7.3 设计原则:先保护事实,再优化吞吐
这类系统的设计原则和普通高吞吐系统不同。这里不是先追求“快”,而是先追求“对”。
7.3.1 在 CAP 里优先保护 CP 核心段
不要教条地说“整个系统必须 CP”,因为互联网交易链路通常仍然包含通知、积分、营销等 AP / 最终一致部分。
更务实的做法是:
- 识别真正的强一致核心段
- 在核心段内优先保证正确性和排他性
- 把外围可延迟的步骤拆出去异步化
例如创单流程里,下面这些更接近强一致核心段:
- 订单金额确认
- 库存预占
- 订单创建
- 支付状态落账
而这些可以作为外围异步段:
- 站内信通知
- 积分发放
- 推荐系统更新
- 营销埋点
7.3.2 缩小强一致边界
很多系统设计失败,不是因为没有一致性方案,而是因为试图把整条链路都做成全局强一致。
正确思路通常是:
- 找出真正不可错的业务事实。
- 只对这些事实建立强约束。
- 其他动作通过事件、补偿和对账来收敛。
这也是为什么成熟系统很少对整条交易链路直接上 XA / 2PC,而是把问题拆成:
- 核心事实在本地事务内原子提交
- 跨系统传播通过 Outbox 或事件驱动保证不丢
- 外围动作用 Saga / 补偿收敛
7.3.3 广泛使用“预占 + 确认 / 取消”模式
这是支付、库存、额度、券核销里最常见也最有效的模型。
它的本质是把一次危险的“直接扣减”拆成两个阶段:
- 先预占资源,防止并发冲突
- 再根据后续结果确认或取消
库存如此,支付授权如此,履约额度如此,优惠占用也如此。
这种模式的优势在于:
- 能缩短真正需要强锁定的时间
- 能把失败恢复变成显式取消动作
- 更容易支持超时释放和审计
7.3.4 幂等、防重、去重必须内建,而不是补丁
强一致场景里最危险的事故,很多不是“完全失败”,而是“成功被执行了两次”。
所以必须默认系统会遇到:
- 请求重试
- 回调重复
- 消息重复投递
- 任务重复执行
- 人工补单重复触发
应对方式不是事后排查,而是把幂等性设计成系统契约:
- 外部请求要有业务幂等键
- 状态迁移要有唯一约束
- 账务流水要有不可重复入账语义
- 补偿动作也必须幂等
7.3.5 审计能力和对账能力不是附属品
只要资金、库存、账本参与进来,系统就必须默认未来一定会出现:
- 下游超时
- 第三方状态不确定
- 多系统状态分叉
- 人工修复需求
所以从第一天起就要准备:
- 权威账本或权威状态源
- 完整操作流水
- 可追踪的状态迁移记录
- 周期性或实时对账机制
没有这些,所谓“强一致”通常只是运行时看起来没问题,出事时却无法解释。
7.4 方案选型:从本地事务到 Saga、TCC 与账本系统
强一致不是只有一种实现方式。不同场景,关键矛盾不同,方案也不同。
7.4.1 本地事务:最强、最简单,也最值得优先保留
如果关键事实能够收敛在一个数据库、一个服务、一个聚合边界里,本地事务永远是第一选择。
适用场景:
- 单服务内订单金额确认
- 单库内库存扣减
- 单账本内余额更新与流水落库
优点:
- 语义清晰
- 成本最低
- 最容易保证 ACID
限制:
- 很难直接跨服务、跨库、跨第三方系统扩展
经验上,只要能通过领域边界调整把关键事实压缩进本地事务,就不要轻易放弃这个机会。
7.4.2 本地事务 + Outbox:跨系统传播的务实主力方案
很多场景真正要解决的问题不是“跨系统一起提交”,而是:
核心事实已经在本地事务里确定了,如何把这个事实可靠传播给其他系统,而且不能丢、不能重复制造错误副作用。
这时很适合使用 Outbox Pattern:
- 在本地事务里同时写入业务事实和待发送事件
- 后台可靠投递事件
- 下游按幂等语义消费
适用场景:
- 支付成功后通知订单系统
- 订单创建后触发履约、通知、积分
- 账务记账后通知报表和风控系统
优点:
- 保留本地事务强语义
- 避免双写不一致
- 更符合互联网系统演进路径
限制:
- 只能保证“本地事实 + 可靠发布”
- 不能替代跨系统同步锁定
7.4.3 预占 + 确认 / 取消:库存、额度、券核销的默认模型
当资源需要先锁住、再根据后续结果决定是否真正消耗时,这种模式通常比直接扣减更稳。
适用场景:
- 库存预占 -> 支付成功后确认 -> 超时后释放
- 钱包冻结 -> 交易成功后扣减 -> 失败后解冻
- 优惠券占用 -> 下单完成后核销 -> 取消后返还
优点:
- 天然适合高并发竞争场景
- 失败恢复路径清晰
- 方便处理超时和人工介入
限制:
- 需要额外状态设计
- 需要后台释放和清理机制
7.4.4 Saga:适合强主控但允许阶段收敛的跨系统流程
如果一条链路必须跨多个系统推进,但又不值得上全局分布式事务,那么 Saga 是很常见的选择。
适用前提:
- 每一步都可以有独立本地事务
- 失败后存在明确补偿动作
- 系统能容忍阶段性中间态
这在创单、退款、售后、履约链路里都很常见。
但要注意:
- Saga 不是强原子提交
- 它依赖状态推进与补偿收敛
- 它更适合“流程一致性”,不适合直接承担“账本绝对原子一致性”
7.4.5 TCC:适合资源竞争强、预留语义清晰的场景
Try-Confirm-Cancel 可以理解成更严格、更结构化的“预占 + 确认 / 取消”。
适用场景:
- 库存预留
- 余额冻结
- 授信额度占用
- 稀缺资源抢占
优点:
- 语义清晰
- 能显式表达资源占用生命周期
- 对高冲突资源友好
限制:
- 对参与方接口设计要求高
- 业务改造成本大
- 并不适合所有外部系统
如果系统本身没有清晰的资源预留语义,硬上 TCC 往往会让实现比问题更复杂。
7.4.6 分布式锁:只解决并发互斥,不解决业务全局一致
很多团队一遇到库存或金额问题,第一反应是“加锁”。这只说对了一小部分。
分布式锁能解决的是:
- 同一资源被并发修改时的互斥问题
但它解决不了:
- 跨系统提交原子性
- 回调重复导致的副作用
- 事件丢失
- 账本对账问题
所以像 Redlock 这样的方案,最多只是强一致系统里的一个局部组件,而不是总方案。
7.4.7 XA / 2PC:理论完整,但互联网主链路要慎用
XA / 2PC 的优点很诱人:多资源统一提交,失败统一回滚。
但现实里的问题也非常明显:
- 长事务影响吞吐
- 锁持有时间长
- 参与方约束强
- 对第三方系统无能为力
- 故障恢复和运维复杂
因此它更适合:
- 受控环境内的少量核心资源
- 内部强约束系统
- 明确能接受性能代价的场景
而不适合作为互联网交易主链路的默认方案。
7.4.8 一张务实选型表
| 场景特征 | 推荐方案 | 说明 |
|---|---|---|
| 关键事实可收敛在单库单服务 | 本地事务 | 优先级最高 |
| 本地事实必须可靠传播给下游 | 本地事务 + Outbox | 解决双写一致性 |
| 稀缺资源需要先锁住后确认 | 预占 + Confirm/Cancel 或 TCC | 典型如库存、额度、冻结余额 |
| 跨多个系统推进且允许阶段中间态 | Saga | 依赖补偿和状态收敛 |
| 只需要解决单资源并发冲突 | 分布式锁 | 只是局部互斥能力 |
| 少量受控资源必须统一提交 | XA / 2PC(慎用) | 用在很窄的边界里 |
7.5 以电商创单为例:哪些必须强一致,哪些可以最终一致
电商创单非常适合用来练这个判断能力。
7.5.1 创单链路拆分
我们把它拆成两层:
- 强一致核心段:价格确认、库存预占、订单创建、支付事实落账
- 最终一致外围段:通知、积分、推荐、报表、营销统计
这一步很关键,因为它决定了系统后面是“能跑”,还是“能长期稳定地跑”。
7.5.2 一个推荐的落地组合
对大多数电商交易系统,比较务实的组合通常是:
- 订单服务内部用本地事务完成订单主记录和状态初始化
- 库存使用预占 + 确认 / 释放模型
- 支付结果以支付系统或账务系统作为权威事实源
- 订单与支付之间通过幂等回调 + Outbox / 可靠事件传播
- 外围通知、积分、营销动作全部异步化
- 异常状态通过查单、补偿、对账和人工接管收敛
这套组合的核心思想是:
- 关键事实不追求“跨所有系统原子提交”
- 而是先保证每个核心事实在自己的边界内绝对正确
- 再通过可靠事件和补偿把全链路收敛起来
7.5.3 为什么不建议“订单、库存、支付三方一起全局事务提交”
因为这样做通常会同时踩中三类问题:
- 性能差:高并发下锁竞争和超时都会放大
- 可用性差:任一方抖动都可能拖垮整条链路
- 现实不成立:第三方支付、回调、跨天操作根本不受你控制
所以成熟交易系统的思路更像:
关键资源先预留,关键事实各自原子提交,跨系统靠事件、查单、补偿和对账闭环。
7.6 支付、库存与账务三类系统的设计重点
7.6.1 支付系统:事实源必须唯一
支付最怕的不是慢,而是不知道到底付没付成功。
所以支付系统设计时,必须明确:
- 谁是支付成功的权威事实源
- 回调、主动查单、本地状态冲突时谁优先
- 同一支付单如何防止重复扣款
推荐做法通常包括:
- 支付单号全局唯一
- 请求幂等键和回调幂等键分开设计
- 支付结果进入账务或订单前先落支付事实流水
- 对“处理中 / 未知”状态建立查单机制
7.6.2 库存系统:重点是防超卖和防重复释放
库存系统的第一原则不是“扣减快”,而是“不能卖出不存在的货”。
因此要优先设计:
- 预占记录
- 并发扣减约束
- 超时释放机制
- 重复确认 / 重复释放幂等
如果库存很稀缺,还要进一步考虑:
- 热点行竞争
- 分片库存
- 扣减排队
- 库存账与业务账对账
7.6.3 账务系统:重点是账本模型,而不是余额字段
账务系统最容易犯的错误,是直接把“余额”当成唯一真相。
更稳的做法是:
- 以账本流水作为权威事实
- 余额是账本汇总结果或受控快照
- 每一笔变更都可追溯、可解释、可重放
这也是为什么高准确性系统里经常强调:
没有流水就没有事实,没有事实就谈不上强一致。
7.7 常见误区
7.7.1 把分布式锁当成一致性总方案
锁只能防并发冲突,不能代替状态机、幂等、账本和补偿。
7.7.2 把 Saga 当成原子提交
Saga 解决的是流程收敛,不是全局瞬时强一致。
7.7.3 把最终一致性当作“不需要定义中间事实”
真正成熟的最终一致系统,恰恰更依赖严格的中间状态定义和对账机制。
7.7.4 只看接口成功,不看业务事实是否落账
第三方返回成功不等于你的业务已经正确落账。
系统必须有自己的事实落地和可验证依据。
7.7.5 只做主链路,不做异常闭环
没有查单、补偿、对账、人工介入入口的强一致系统,通常只是在“理想路径上看起来完整”。
7.8 方法论沉淀:如何做出务实选型
最后把本章压缩成几条可以复用的判断。
7.8.1 五个判断句
- 只要业务结果一旦出错就会形成资损或合规风险,就要优先按高准确性场景来设计。
- 真正需要强一致的,通常不是整条链路,而是链路里的核心事实段。
- 本地事务永远优先于跨系统强一致,能缩边界就不要扩边界。
- 预占 + 确认 / 取消,是支付、库存、额度、券核销中最通用的资源控制模型。
- 真正能支撑生产的,不只是“写时一致”,还包括幂等、防重、查单、补偿、对账和审计。
7.8.2 一句话表达
如果要把这一章压缩成一句可直接复用的话,可以这样讲:
高准确性与强一致性系统设计的核心,不是把整条链路做成昂贵的全局原子事务,而是先识别不可错的业务事实,再用本地事务、预占模型、幂等、防重、账本和可靠事件,把核心事实保护住、把外围流程收敛住。