从几个缩写讲起

首先,提到事务,一般指的是数据库的事务,指逻辑上的一组操作,要么都执行,要么都不执行。

ACID,指的是数据库在写入或者更新资料时,为了保证交易正确可靠,要具备的4个特性:

缩写 英文单词 中文解释 说明
A atomicity 原子性 最小执行单位,all or nothing
C consistency 一致性 执行前后一致
I isolation 隔离性 并发时,事务间不干扰
D durability 持久性 持久改变

这里要特别注意,C一致性是最终的目的,其余三个是实现C的手段。在单机上实现ACID可以通过锁、时间序列等机制。

接下来是分布式事务,与微服务密切相关,因为不同的微服务一般会使用自己的数据库,这个时候要满足ACID就比较困难了,如何保证系统中多个相关联的数据库中的数据一致?

此时,需要选择折中的方案,为此,引进了CAP理论:

缩写 英文单词 中文解释 说明
C consistency 一致性 所有节点访问同份最新数据副本,要么返回最新数据要么失败
A availability 可用性 非故障节点在合理时间返回合理响应,不保证数据一致
P partition tolerance 分区容忍性 出现网络分区时仍对外提供服务

分布式系统必须保障能够对外提供服务,即分区容错性是必须的。不可能三角指的是在读写操作时,假设出现了网络分区,只能满足两个,即CP或者AP。这里要特别注意,如果没有出现网络分区,A和C是可以同时满足的。当数据不一致会影响业务时,选择CP,当业务需要高可用时,选择AP。常用的注册中心中,Zookeeper保证了CP,Eureka保证了AP,Nacos二者都支持。

在C和A的权衡实践中,诞生了BASE理论:

缩写 英文单词 中文解释 说明
B basically available 基本可用性 允许损失部分可用性(响应时间延长,损失部分非核心功能等)
A availability 可用性
S soft-state 软状态 允许数据不一致,不影响整体可用性
E eventually consistent 最终一致性 一致的三个级别

这里需要理清一致性的3个级别:

  1. 强一致性:在银行等场景需要保证;
  2. 弱一致性:什么时候达到一致的状态完全没有保证(所以基本不用);
  3. 最终一致性:系统保证在一定时间内达到一致,业界比较推崇,那么如何保证最终一致性:
    1. 读时修复
    2. 写时修复(性能好)
    3. 定期修复(常用)

分布式事务的解决方案

在分布式系统中,如何保障各个节点之间的ACID特性?主要解决方案可分为两大类:

1. 强一致性方案

  • 二阶段提交协议(2PC)
  • 三阶段提交协议(3PC)

2. 最终一致性方案

  • 补偿事务(TCC)
  • MQ事务
  • Saga事务
  • 本地消息表

其中:

  • 2PC、3PC 属于业务代码无侵入方案,基于 XA 规范衍生而来。
  • TCC、Saga 属于业务入侵方案,需要开发者手动实现补偿逻辑。
  • MQ 事务依赖于消息队列,本地消息表不支持回滚。

强一致性方案(2PC & 3PC)

XA规范

根据XA规范设计,首先介绍XA规范涉及的角色:

image-20250324173851890

  • AP(Application Program):应用程序
  • RM(Resource Manager):资源管理器(通常指数据库,也有文件系统、MQ系统),提供操作数据的接口等,保证数据一致和完整
  • TM(Transaction Manager):事务管理器,是一个协调者的角色,协调跨库事务关联的所有RM的行为

2PC(两阶段提交)

  1. 准备阶段(Prepare)
    • TM 记录事务开始日志,询问所有 RM 是否可以执行提交准备操作。
    • RM 尝试执行本地事务的预备操作:锁定资源,执行事务但不提交。[if 失败]则告知TM,并回滚自己的操作,不参与本次事务。
    • TM 收集RM的响应,记录事务准备完成日志。
  2. 提交阶段(Commit)
    • 若所有 RM 均准备成功,TM 通知 RM 提交事务。
    • 若有失败,则 TM 让所有 RM 回滚事务

问题:

  • 阻塞问题:等待提交时,资源被锁定。
  • 单点故障:如果 TM 宕机,可能导致事务卡住。

3PC(三阶段提交)

对 2PC 进行了改进,增加了超时机制。

  1. CanCommit(准备阶段)
    • TM 询问 RM 是否可提交事务。
    • RM 返回 Yes / No / 超时。
  2. PreCommit(预提交阶段)
    • 若所有 RM 均返回 Yes,TM 发送预提交请求。
    • RM 预执行事务,等待最终确认。
    • 若有 RM 失败或超时,则 TM 发送中断请求。
  3. DoCommit(提交阶段)
    • 若所有 RM 均完成预提交,TM 发送最终提交请求。
    • 进入该阶段后,基本不会失败。

改进点

  • 增加超时机制,避免事务永久阻塞。
  • 通过 CanCommit 阶段减少资源长时间锁定。但是解决并不完美,性能差、数据仍然不一致,应用不广泛,一般会通过复制状态机解决2PC的阻塞问题。

最终一致性方案(TCC & Saga & MQ & 本地消息表)

TCC(Try-Confirm-Cancel)

适用于高并发、低延迟的业务场景,例如支付系统。

  1. Try(尝试执行):进行业务检查,预留资源。
  2. Confirm(确认执行):若所有 Try 操作成功,则正式提交。
  3. Cancel(取消执行):若某个 Try 失败,则执行回滚操作。

注意:

  • 需要业务开发者自己实现 Try、Confirm、Cancel 逻辑。
  • Confirm 失败时一般会重试,最终仍失败则需人工介入。

MQ 事务

基于 两阶段提交,适用于 事件驱动架构。

  1. 发送半消息,等待本地事务执行。
  2. 本地事务执行成功,则确认发送消息;失败则回滚消息。
  3. 事务反查机制:检查消息是否成功发送。
  4. 消息消费失败时,消息队列会自动进行重试,超过最大次数进入 死信队列,等待人工处理。

特点:

  • 适用于 跨系统事务(如支付完成后通知订单系统)。
  • 异步处理,吞吐量高,但不保证强一致性。

Saga 事务

适用于 长事务(如电商订单流程:支付 → 发货 → 确认收货)。

  • 将事务拆分为 多个子事务,每个子事务执行完即提交。
  • 若某个子事务失败,则触发 补偿事务 进行回滚。
  • Saga 事务没有预留资源,不保证隔离性。
  • Seata 是典型的 Saga 事务实现。

问题:

  • 需要业务开发者自己编写补偿逻辑。
  • 若补偿失败,则需人工介入。

本地消息表

适用于 保证可靠消息的场景。

  • 事务执行时,先写入数据库本地消息表。
  • 定时扫描消息表,将消息投递到 MQ。
  • 缺点不支持事务回滚,需额外补偿机制。

方案对比总结

方案 业务代码侵入性 适用场景 关键特性
2PC 无侵入 传统数据库事务 强一致性,阻塞问题
3PC 无侵入 分布式数据库事务 改进 2PC,仍有不一致风险
TCC 需要开发者实现 高并发业务,如金融支付 高性能,需要 Try-Confirm-Cancel
Saga 需要开发者实现 长事务,如订单流程 适用于多步骤事务,无隔离性
MQ 事务 依赖 MQ 事件驱动架构 事务解耦,不保证 ACID
本地消息表 依赖数据库 可靠消息 无法回滚,需要补偿

总结:

  • 强一致性:2PC / 3PC,适用于对事务要求极高的场景。
  • 最终一致性:TCC / Saga / MQ / 本地消息表,适用于高吞吐量或长事务。
  • TCC / Saga 需要开发者手动管理事务,2PC / 3PC 由事务管理器自动处理。

选择哪种方案,取决于业务需求、性能要求以及对一致性的容忍度。

// 待补充学习

补充:分布式系统的开发中,延迟是个很重要的指标。评估服务可用性–>负载均衡和容灾;评估领导者节点可用性–>是否发起领导选举。

参考资料:

  1. 如何选择分布式事务解决方案?
  2. 服务治理:分布式事务解决方案有哪些?

—某天职场老兵William突然抽查我的八股基础,回来赶紧灰溜溜补上……