付同学的技术客栈/

分布式一致性:系统越拆,正确性越贵

所属专题 高并发海量数据系统架构 第 6 篇 / 共 11 篇 查看专题目录

系统没拆开之前,下单这件事很简单。

一个数据库事务里,创建订单、扣库存、核销优惠券。提交成功就是成功,回滚失败就是失败,边界很清楚。

后来系统拆开了。订单是订单服务,库存是库存服务,优惠券是营销服务,支付回调又来自外部系统。一次下单跨了多个服务、多个数据库、多个消息。

于是问题来了:订单创建成功了,库存扣了,优惠券核销失败怎么办?支付成功了,积分重复到账怎么办?消息发出去了,消费者处理到一半挂了怎么办?

系统越拆,正确性越贵。

一致性问题最磨人的地方,是它经常不是马上错。

用户下单成功了,库存稍后才扣;支付回调到了,订单状态还没更新;积分发放失败了,但主流程已经走完。单看每一步都能解释,串起来就变成了“为什么用户看到的和后台看到的不一样”。

这种问题一旦进了客服和运营群,技术解释通常很难听。你说异步、重试、最终一致,对方关心的是:这笔订单到底算不算成功,用户要不要补偿,数据以后还能不能信。

所以分布式一致性不是只在讨论 CAP,它最后会落到业务可信度上。

现场判断 一致性问题最后不是理论题,而是业务还能不能相信系统里的状态。

订单、库存、支付、积分只要拆开,就要提前想清楚失败、重试和补偿怎么把结果收回来。

一、不要一上来就追求强一致

分布式事务可以追求强一致,但代价很高。

两阶段提交、XA 这类方案能让多个资源一起提交或回滚,但会带来锁持有时间长、性能下降、可用性受影响、实现复杂等问题。

很多业务真正需要的,不是每一秒都绝对一致,而是最终能回到正确状态。

比如订单已创建、积分稍后到账;支付成功、通知稍后补发;库存预扣成功、超时未支付再释放。

最终一致不是“不一致也没关系”,而是允许短时间处于中间状态,但必须有机制把状态收敛回来。

二、TCC 和 Saga 不是越复杂越好

TCC 是 Try、Confirm、Cancel。

它适合资源预留明确、业务状态可控的场景。比如库存先冻结,支付成功后确认扣减,超时或失败后取消冻结。

TCC 的控制力强,但业务侵入也强。每个参与方都要实现 Try、Confirm、Cancel,还要处理幂等、空回滚、悬挂等边界情况。

Saga 更像一组本地事务加补偿动作。

比如创建订单、扣优惠券、发积分。如果后面失败,就执行反向补偿。它适合长流程业务,但补偿不一定能完全恢复原状。

选择时要回到业务:

  • 资源预留明确,正确性要求高,可以考虑 TCC。
  • 流程较长,允许补偿,可以考虑 Saga。
  • 只是异步通知或衍生数据,用消息最终一致通常更简单。

三、本地消息表和 Outbox 很朴素,但很有用

很多一致性问题卡在一句话:数据库写成功了,但消息没发出去。

比如订单创建成功后要发订单创建事件。如果数据库提交成功,但 MQ 发送失败,下游永远不知道这件事。

本地消息表和 Outbox 的思路是,把业务数据和待发送消息写入同一个本地事务。

本地事务:
  写订单
  写 outbox 消息

后台任务:
  扫描未发送消息
  投递 MQ
  成功后标记已发送

这样至少能保证:只要订单写成功,消息就一定有记录,后面可以重试投递。

它不花哨,但工程上很稳。

收敛路径 最终一致不是放任不一致,而是给每一步都留回收机制
本地状态 先把订单、库存或支付状态写清楚。
可靠消息 通过 Outbox 或本地消息表保证事件不会凭空丢掉。
幂等消费 重复投递、重复补偿都不能把业务写乱。
状态收敛 重试、补偿、人工处理,把中间状态拉回正确结果。

系统拆得越开,越要把“怎么收回来”写进设计里。

四、分布式锁只能解决一部分问题

分布式锁经常被拿来解决并发问题,但它不是一致性的万能答案。

它适合控制同一资源在同一时间只有一个操作,比如防止重复提交、热点资源更新、定时任务多实例抢占。

但分布式锁也有风险:锁超时、锁误删、业务执行时间超过锁租约、网络抖动、锁服务不可用。

所以锁要有超时时间,释放锁要校验持有者,业务要能幂等。更重要的是,不要把所有一致性问题都交给锁。

很多时候,状态机、唯一约束、版本号、幂等表,比一把锁更可靠。

五、幂等是最终一致的底座

只要系统里有重试,就必须有幂等。

消息会重复投递,接口会重复调用,补偿会重复执行。没有幂等,重试就可能把系统越修越坏。

常见幂等方式包括:

  • 业务唯一号。
  • 唯一索引。
  • 状态机流转。
  • 版本号或乐观锁。
  • 幂等记录表。

我更喜欢用业务状态做约束。比如订单只能从“待支付”变成“已支付”,不能从“已关闭”再变成“已支付”。状态机设计清楚,很多异常请求自然会被挡住。

六、可以带走的判断清单

  • 先判断业务是否真的需要强一致。
  • 能接受短暂中间状态,就优先考虑最终一致。
  • TCC 适合明确资源预留,但业务侵入强。
  • Saga 适合长流程,但补偿要设计清楚。
  • 业务数据和消息要可靠关联,可以考虑 Outbox。
  • 有重试就必须有幂等。
  • 状态机和唯一约束经常比一把分布式锁更稳。

分布式一致性不是追求理论完美,而是让系统在失败、重试和异步中还能回到正确状态。


本系列:

高并发海量数据系统架构 第 6 篇 / 共 11 篇
查看专题目录

留下一条评论

暂无评论