1. 什么是事务

事务(Transaction)的概念起源于数据库领域,最早由美国计算机科学家 E. F. Codd 在其关于关系数据库(Relational Database)的论文中提出。

他提出了 ACID(原子性、一致性、隔离性和持久性)属性,这些属性成为事务的核心特征。

在今天的软件开发中,事务的概念已不仅仅应用于数据库领域,还拓展到了业务开发的各个领域,包括但不限于数据库、缓存、消息队列等。

1.1 ACID 特性

  • 原子性(Atomicity): 保证事务中的所有操作要么全部完成,要么全部不发生,有助于处理系统错误或故障时的数据恢复,确保事务执行的完整性。
  • 一致性(Consistency):系统从一个正确态转移到另一个正确态,由应用通过 AID 来保证,可以说是事务的核心特性
  • 隔离性(Isolation): 处理并发事务带来的各种问题,确保每个事务看到的是一致的数据视图,防止交叉事务的干扰。
  • 持久性(Durability): 确保事务一旦提交,其结果就就会被持久化,这保证了数据的稳定性和可靠性。

定义本身不再赘述,这里重点强调一点:一致性是事务的核心特征,或者说最终目的。原子性、隔离性和持久性都是实现一致性的手段,因此这 4 个特性并不是并列关系。

2. 事务的分类

下面将把事务按照服务和数据源数量进行分类,这种分类有助于理解事务管理的复杂性以及在不同场景下的设计和实现。

2.1 本地事务-单服务单数据源事务

在实际业务开发中,单个服务操作单个数据源的事务被归类为本地事务。这种事务类型是最简单的,因为它直接依赖于数据库本身的事务能力来完成,应用无需进行额外操作

示例:
库存服务:当用户下单时,库存服务负责检查和更新商品库存。这个服务可能只与一个库存数据库交互,进行减库存的操作。如果库存足够,事务提交,否则回滚。这个操作只涉及库存数据库,因此是一个典型的本地事务。

2. 2 分布式事务

分布式事务可以从跨多个数据源的事务和跨多个服务的事务两个角度理解。它既可以是多个数据库实例之间的分布式事务,也可以是跨不同中间件的业务层面分布式事务。

2.2.1 单服务多数据源

这种情况通常发生在单个应用或服务需要同时操作多个数据库或存储系统。

例如,一个电子商务应用可能需要在处理订单的同时,在一个数据库中更新库存信息,在另一个数据库中更新用户账户信息。这要求事务管理机制能够跨越这些数据库,确保所有数据库操作要么全部成功,要么全部失败,以保证数据的一致性

在这种场景下,可以使用如XA协议这样的分布式事务协议,通过2PC等机制来协调和管理跨多个数据源的事务。

2.2.2 多服务多数据源

随着微服务架构的发展,单个业务操作往往需要多个微服务协作完成,而这些服务可能各自使用独立的数据库。例如,在电商下单过程中,订单服务、库存服务、账务服务、物流服务和优惠服务需要协同处理同一业务请求,并进行交互和数据更新。

在这种场景下,分布式事务的管理比单个服务场景更为复杂,因为它不仅涉及数据一致性,还涉及网络调用的可靠性和服务间的协调。这类分布式事务通常可以通过可靠消息队列、TCC 和 SAGA 等模式来实现。

2.3 共享事务-多服务单数据源

在微服务架构下,通常不允许多服务共享同一数据源。理想的微服务架构是每个微服务都有其专属数据库(即服务与数据源一一对应),这种设计被称为数据库隔离。

因此,本文及本系列不会涉及该类型事务。

3. 两种分布式事务的区别

在事务分类中,单服务多数据源多服务多数据源 都被归类为分布式事务,那么这两种分布式事务有什么区别呢?

首先,单服务多数据源事务是多个数据库实例之间的分布式事务, 也被称为全局事务。当它被称为分布式事务时,这里的“分布式”是相对于数据源而言的,并不涉及服务。

而多服务多数据源事务是跨不同中间件的业务层面分布式事务。

这两种分布式事务的一个重要区别在于一致性的实现方式不同:

  • 单服务多数据源事务通常可以追求 强一致性
  • 多服务多数据源事务由于其复杂性和分布式特性,通常只能追求 最终一致性

下面将详细解释这两种情况及其原因。

3.1 单服务多数据源 与 强一致性

在单服务多数据源的场景中,尽管涉及多个数据源,但所有操作都由一个单一服务控制。这种配置允许使用两阶段提交(2PC)等传统的分布式事务协议来确保强一致性,即在任何时刻,所有数据源都能反映出相同的事务状态。

为什么可以实现强一致性:

  • 集中式协调:单个服务可以作为事务的中央协调者,管理所有数据源的事务提交或回滚。
  • 锁定资源:事务处理过程中可以在各个数据源上锁定必要的资源,直到事务完成,确保事务的原子性和一致性。
  • 同步更新:所有数据源的更新操作可以同步进行,确保在事务提交时,所有的变更都能一次性反映出来。

3.2 多服务多数据源 与 最终一致性

多服务多数据源事务涉及多个独立的服务,每个服务可能管理自己的数据源。在这种架构下,实现强一致性变得非常复杂和成本高昂,因此通常采用最终一致性模型。

为什么通常只能实现最终一致性:

  • 服务自治:每个服务都是自治的,独立管理自己的数据源,它们之间的通信可能是异步的,不能立即反映其他服务的状态变更。
  • 复杂的协调机制:需要跨服务协调复杂的事务可能涉及网络延迟和服务间通信失败,使得同步更新所有数据源变得不切实际。
  • 使用补偿事务:多服务事务常采用如SAGA等模式,通过一系列的本地事务和补偿事务来处理业务流程,每个事务独立提交,仅通过补偿机制来撤销错误操作,逐步达到数据的一致性。

4. 强一致性 vs 最终一致性

4.1 一致性的分类

4.1.1 强一致性(Strong Consistency)

强一致性意味着系统在更新数据后,任何随后的访问都将立即看到这一更新。在强一致性模型中,所有节点上的数据在任何时间点都是一致的。这通常要求在数据更新过程中进行严格的协调,确保所有副本在继续操作前都同步更新。

优点:

  • 数据一致性和用户体验最为理想。
  • 易于理解和使用,因为它模拟了单个系统的行为。

缺点:

  • 可能严重影响系统的可用性和性能,尤其在网络延迟较高的情况下。
  • 在 CAP 定理中,通常需要在遇到网络分区时牺牲可用性。

    4.1.2 线性一致性(Linearizability)

线性一致性是强一致性的一个特例,它不仅保证所有节点看到相同的数据,还要求系统表现得就像所有操作都是顺序发生的。这意味着如果操作A在操作B之前完成,那么系统中的所有节点都应该首先看到A的结果,然后是B的结果。
优点

  • 提供了强一致性的最高标准,适用于需要严格数据顺序的应用。
  • 简化了系统的编程模型。
    缺点
  • 对系统性能和可用性的影响比一般的强一致性还要大。

4.1.3 弱一致性(Weak Consistency)

弱一致性不保证在数据更新后立即反映这一变化。在更新操作和其影响被所有用户观察到之间,存在一个不确定的时间窗口。这种模型通常用于对实时一致性要求不高的系统。
优点

  • 提高了系统的可用性和性能。
  • 在处理高并发操作时更加有效。
    缺点
  • 用户可能会读到旧数据。
  • 应用逻辑可能需要处理数据不一致的问题。

    4.1.4 最终一致性(Eventual Consistency)

最终一致性保证,在没有新的更新的情况下,所有的数据副本最终将会是一致的。系统不保证达到一致状态的具体时间。
优点

  • 高度可用和可扩展。
  • 适用于分布广泛的系统,可以容忍数据在短时间内的不一致。
    缺点
  • 应用需要能够处理数据一段时间内的不一致。
  • 开发者需要设计有效的数据同步和冲突解决策略。

    4.2 CAP 与 ACID的微妙平衡-分布式系统只能追求最终一致性

根据 CAP 定理,一个分布式系统不可能同时满足一致性(Consistency)、可用性(Availability)和分区容忍性(Partition Tolerance)三个属性,最多只能满足其中两个,必须牺牲一个。

  • 一致性(Consistency):在任何时刻,任何分布式节点中看到的数据都保持一致。
  • 可用性(Availability):系统能够不间断地提供服务的能力。
  • 分区容忍性(Partition Tolerance):在分布式环境中,当部分节点因网络原因失联(即形成“网络分区”)时,系统仍能正确提供服务的能力。

4.2.1 为什么说 分布式系统 必须接受 分区容忍性

理解为什么分区容忍性在分布式环境下必然存在,需要从分布式系统的基本构成和网络通信的不可靠性两个角度探讨。

  1. 分布式系统的基本构成 分布式系统由多个相互协作的独立组件组成,这些组件可能位于物理上分散的不同位置。该架构的主要优势是提高系统的可扩展性、容错性和资源利用率。然而,这也意味着系统的各个部分必须通过网络通信。

  2. 网络通信的不可靠性 网络本身存在不可靠性,可能因多种原因导致通信失败:

    1. 网络故障:网络设备或连接可能出现故障,如路由器故障、连接断开等。
    2. 网络延迟:消息在传输过程中可能遭遇不可预测的延迟。
    3. 带宽限制:网络的带宽限制可能导致数据包延迟到达或丢失。
    4. 网络安全:网络攻击(如分布式拒绝服务攻击,DDoS)可能导致网络部分或完全不可用。

如果一个系统设计选择不接受网络分区,那么一旦网络分区发生,系统将无法正常工作,这在大多数业务场景中是不可接受的。

因此,在分布式系统中,分区容忍性(Partition Tolerance)是必然存在的特性。

基于分区容忍性必须满足的现状以及 CAP 理论,系统只能在一致性和可用性之间做出选择。通常,系统会选择高可用性,强一致性因此被牺牲,系统只能追求最终一致性。

5. 理解分布式事务中的各种协议

5.1 DTP 模型和 XA 规范

5.1.1 DTP 模型

DTP(Distributed Transaction Processing,分布式事务处理)模型是由 X/Open(后来的 Open Group)提出的一种分布式事务处理架构模型。它定义了一套标准,使得不同厂商的分布式事务处理系统能够互操作。

在标准的 DTP 模型中,定义了以下四个主要组件:

  1. Application Program(AP,应用程序):

    • 发起分布式事务的主体,由最终用户或开发者编写。
    • 通过调用事务管理器的接口(例如 TX 接口)开始、提交或回滚事务。
    • 应用程序与事务管理器和资源管理器交互。
  2. Transaction Manager(TM,事务管理器):
    • 负责管理分布式事务的开始、提交和回滚等操作。
    • 维护事务的状态,并使用两阶段提交协议(2PC)协调所有参与的资源管理器。
    • 提供对外的 TX 接口供应用程序使用,并通过 XA 接口与资源管理器交互。
  3. Resource Manager(RM,资源管理器):
    • 负责管理和控制对特定资源的访问,例如数据库管理系统(DBMS)、文件系统、消息队列等。
    • 接收事务管理器的请求以进行资源操作,并确保数据一致性。
    • 实现 XA 接口与事务管理器通信。
  4. Communication Resource Manager(CRM,通信资源管理器):
    • 可选组件,负责管理与外部系统的通信资源。
    • 在分布式事务中协调和同步事务状态,确保跨系统的事务一致性。
    • 管理跨网络的事务传播,确保分布式环境中的事务处理一致性。

主要接口:

  1. TX 接口:
    • 应用程序 AP 与事务管理器 TM 之间的桥梁,负责事务的开始、提交和回滚等操作。
    • 例如,在 Java EE 中,TX 接口通常对应 javax.transaction.UserTransaction
  2. XA 接口:
    • 事务管理器 TM 与资源管理器 RM 之间的接口,协调资源管理器在两阶段提交协议中的操作。
    • 常见的 XA 接口方法包括 xa_openxa_startxa_endxa_preparexa_commitxa_rollback 等。
  3. CRM 接口:
    • 事务管理器与通信资源管理器之间的接口,确保分布式事务在网络通信中保持一致性。
    • 没有明确的标准接口,由各系统厂商自行实现。

5.1.2 XA规范

XA 规范是 X/Open 组织在 DTP(Distributed Transaction Processing)模型中定义的,用于描述事务管理器(TM)和资源管理器(RM)之间交互的接口标准。

  1. 接口标准:
    XA 规范定义了一套标准接口,包括 xa_startxa_endxa_preparexa_commitxa_rollback 等。

  2. 2PC 协议:
    XA 接口实现了两阶段提交协议(2PC),以确保分布式事务的一致性和完整性。

5.1.3 XA 事务

XA事务是一种分布式事务。通过两阶段提交协议和XA接口标准,事务管理器和资源管理器能够可靠地协同工作,实现跨系统的事务处理,确保多个独立资源的一致性。

实际应用

  1. 数据库系统:
    • 大多数主流数据库系统都支持XA事务,如Oracle、MySQL、DB2、SQL Server等。
    • 通过实现XA接口,数据库可以参与分布式事务并与事务管理器协同工作。
  2. 消息中间件:
    • 一些消息队列和消息中间件也支持XA事务,如IBM MQ、ActiveMQ等。
    • 能够确保消息发送与其他资源操作的一致性。
  3. Java EE环境:
    • 在Java EE应用程序中,javax.transaction.UserTransactionjavax.transaction.TransactionManager接口提供了对XA事务的支持。

      5.2 两阶段提交(2PC)

两阶段提交是一种具体的事务协议,用于在分布式系统中协调多个事务参与者的行为,以确保事务的原子性。它包含以下两个阶段:

  • 准备阶段:协调者询问所有参与者,是否准备好提交事务。
  • 提交/回滚阶段:基于各参与者的答复和超时情况,协调者决定是否全局提交或回滚,
    • 只有全部参与者回答了prepared 才会commit;
    • 若有一个参与者回答和non-prepared 或者超时未回答,则rollback

5.2.1 协调者宕机:单点问题,参与者阻塞

在2PC中,一个重要特点是参与者缺乏超时机制。因此,在第一阶段结束后,他们必须原地等待协调者的第二阶段指令。一旦协调者宕机,所有参与者都会受到影响。如果协调者长时间未恢复或未发送正常的提交或回滚指令,所有参与者都将被阻塞。

为何参与者缺乏超时处理机制呢?因为这可能引发数据一致性问题。当参与者迟迟未收到提交或回滚指令时,无论其默认为提交还是回滚,都可能导致全局数据不一致。

这也给了我们业务开发一些启示:在任何不确定情况下,都不应随意指定默认操作,最佳做法是启动警报,让人工介入处理。

5.2.2 回滚性能差

所有的操作都已经完成,回滚需要全部推翻。

5.2.3 一致性问题

5.2.4.1 协调者宕机

如上面单点问题中描述,协调者宕机后,由于参与者没有超时处理机制,会一直阻塞等待,直到协调者宕机恢复后, 根据持久化的数据判断该事务状态,进而发送commit 或者 rollback , 所以在协调者宕机恢复前 协调者和参与者的数据是不一致的

5.2.3.2 参与者宕机

如果参与者收到commit后,宕机了。此时数据也是不一致的
参与者宕机恢复后,可以检查自己的持久化信息,来判断事务的状态。

5.2.3.3 网络问题

有的参与者收到了commit,有的参与者收不到;
参与者的ack 消息,协调者有的收到了,有的没收到。
其中参与者收不到第二阶段的消息,自然不会有ack, 表现上也是协调者收不到ack。
这里的解决方案就是 协调者超时处理机制-重试,在重试成功之前,数据是不一致的。

5.2.4 梳理下 DTP、XA、2PC 之间的关系

DTP(Distributed Transaction Processing,分布式事务处理)模型是由X/Open(后来的Open Group)提出的一种分布式事务处理的体系结构模型。它定义了一套标准,使得不同厂商的分布式事务处理系统能够互操作。

XA规范是X/Open组织 在DTP(Distributed Transaction Processing)模型中定义的,用于描述事务管理器(TM)和资源管理器(RM)之间的交互的接口标准。

XA 规范基于2PC 实现。

但是 2PC协议是一种通用的事务提交协议,可以在任何实现中使用。除了XA规范,2PC协议还可以用于其他事务管理协议和框架,如:

  1. Seata:阿里巴巴开源的分布式事务框架,提供全局事务管理服务,支持2PC但不直接使用XA接口。
  2. Atomikos:支持两阶段提交协议的独立事务管理器。
  3. Bitronix:另一个独立事务管理器,也支持2PC协议。
  4. 在某些场景下,可以直接在应用程序代码中实现简化版的2PC协议,而无需遵循XA规范。

5.3 三阶段提交(3PC)

3PC 的3个阶段,

  1. CanCommit
  2. PreCommit
  3. DoCommit

3PC 相比2PC 的变化

  1. 3PC提交把2PC的prepare 阶段细分为两个阶段,分别称为 CanCommit、PreCommit
  2. 参与者增加了超时处理机制,超时默认会提交事务

3PC 的提出是为了改进2PC 存在的问题

5.3.1 CanCommit 优化回滚操作性能

新增的 CanCommit 是一个询问阶段,协调者让每个参与的数据库根据自身状态,评估该事务是否有可能顺利完成。这可以解决提高precommit 阶段的成功率,万一失败了,回滚操作也比较轻,因为还没开始做实质性的操作

但是这里要注意一个性能问题,在事务需要回滚的场景中,三段式的性能通常要比两段式好很多,但在事务能够正常提交的场景中,两段式和三段式提交的性能都很差,三段式因为多了一次询问,性能还要更差一些。

5.3.2 解决协调者单点问题

通过增加参与者超时处理机制,默认会提交事务,相当于解决了协调者宕机参与者阻塞等待的单点问题

5.3.3 加重数据一致性问题

在2PC中已经讨论过,为什么2PC参与者没有超时处理机制?
因为超时处理机制可能引发数据一致性问题,当 参与者迟迟收不到commit or rollback 指令时, 参与者不论是 默认提交 还是默认回滚,都有可能导致全局数据不一致。

3PC 增加了超时机制, 会默认提交事务,这会加重数据一致性的问题

5.4 TCC(Try-Confirm/Cancel)

TCC是一种应用层事务协议,它分为三个阶段:Try(尝试)、Confirm(确认)、Cancel(取消)。在Try阶段,每个参与者尝试执行事务并锁定必要资源;在Confirm阶段,如果所有参与者的Try操作都成功,那么执行Confirm操作提交事务;如果任何Try失败,则执行Cancel操作回滚事务。TCC适用于业务逻辑复杂,需要长时间运行的事务。

个人认为,TCC可以被理解为是2PC的一种变体,具有两阶段的结构,但它在实施和操作上更适合处理复杂的业务逻辑和提高系统的灵活性与效率。

5.5 可靠消息队列

使用可靠消息队列来解决分布式事务问题是一种被称为“最终一致性”的策略,它通过异步消息传递的方式,确保在分布式系统中多个服务之间的数据一致性。

使用可靠消息队列解决分布式事务的核心思想在于:

  1. 异步与最终一致性:通过异步的方式处理分布式事务,并确保最终一致性。
  2. 可靠消息传递:确保消息传递的可靠性,包括重试机制、幂等处理等。

5.6 SAGA

SAGA是一种将长期事务分解为一系列较小的、独立的子事务的方法。每个子事务都可以单独提交或回滚。如果某个子事务失败,SAGA通过执行补偿事务(即逆操作)来恢复之前的状态。SAGA降低了资源锁定的时间,适用于微服务架构中的事务管理。

参考文章
《周志明的软件架构课》
https://www.51cto.com/article/648668.html