一个可用的复杂的系统总是从可用的简单系统进化而来。反过来这句话也正确: 从零开始设计的复杂的系统从来都用不了,也没办法让它变的可用。 --John Gal 《系统学》 1975
1. 事务的概念
百科:
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
1.1 事务的ACID特性
1.1.1 [A]原子性 Atomicity
事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做。
1.1.2 [C]一致性 Consistency
一致性有两层语义,一层是确保事务执行结束后,数据库从一个一致状态转变为另一个一致状态。另一层语义是事务执行过程中的中间状态不能被观察到。如果数据库中有没有执行完的事务,那就是不一致的,否则,就是一致的。即事务必须被执行完。
- 举个银行转账的例子: A有90块钱, 他要向B转100块钱, 如果可以转帐成功A余额为负数, 我们银行是不允许账户余额为负数,所以转账就不能成立。如果转账成功,虽然他没有破坏数据库的约束,但是他破坏了我们应用层的约束,所以事务发生了回滚,也可以说事务提供了一致性的保证。
1.1.3 [I]隔离性 Isolation
一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
事务隔离性问题:
脏读 Dirty read
事务A修改了一个数据,但未提交,事务B读到了事务A未提交的更新结果,如果事务A提交失败,事务B读到的就是脏数据。
不可重复读 Unrepeatable read
在同一个事务中,对于同一份数据读取到的结果不一致。比如,事务B在事务A提交前读到的结果,和提交后读到的结果可能不同。不可重复读出现的原因就是事务并发修改记录,要避免这种情况,最简单的方法就是对要修改的记录加锁,这会导致锁竞争加剧,影响性能。另一种方法是通过MVCC可以在无锁的情况下,避免不可重复读。
幻读 Phantom read
在同一个事务中,同一个查询多次返回的结果不一致。事务A新增了一条记录,事务B在事务A提交前后各执行了一次查询操作,发现后一次比前一次多了一条记录。幻读是由于并发事务增加记录导致的,这个不能像不可重复读通过记录加锁解决,因为对于新增的记录根本无法加锁。需要将事务串行化,才能避免幻读。不可重复读和幻读的区别: 链接
事务的隔离级别
读未提交 Read Uncommitted(脏读☑️、不可重复读☑️、幻读☑️)
最低的隔离级别,什么都不需要做,一个事务可以读到另一个事务未提交的结果。所有的并发事务问题都会发生。
读已提交 Read Committed(脏读✖️、不可重复读☑️、幻读☑️)
只有在事务提交后,其更新结果才会被其他事务看见。可以解决脏读问题。
可重复读 Repeated Read(脏读✖️、不可重复读✖️、幻读☑️)
在一个事务中,对于同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。可以解决脏读、不可重复读。
串行化 Serialization(脏读✖️、不可重复读✖️、幻读✖️)
事务串行化执行,隔离级别最高,牺牲了系统的并发性。可以解决并发事务的所有问题。
Dirty read | Unrepeatable read | Phantom read | |
---|---|---|---|
Read Uncommitted | ☑️ | ☑️ | ☑️ |
Read Committed | ✖️ | ☑️ | ☑️ |
Repeated Read | ✖️ | ✖️ | ☑️ |
Serialization | ✖️ | ✖️ | ✖️ |
通常在项目中,为了考虑性能,会取这种的隔离级别,例如Mysql默认的隔离级别就是 可重复读。
Mysql 使用 InnoDB存储引擎在可重复读事务隔离级别下使用的是Next Key Lock 锁算法,因此可以避免幻读的产生,实际上达到了Serialzable的效果。
不同数据库的默认隔离等级
Database | default isolation level | Dirty read | Unrepeatable read | Phantom read |
---|---|---|---|---|
Oracle | Read Committed | ✖️ | ☑️ | ☑️ |
Mysql | Repeated Read | ✖️ | ✖️ | ✖️ |
Postgresql | Read Committed | ✖️ | ☑️ | ☑️ |
1.1.4 [D]持久性 Durability
持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
2. 事务的类型
事务可以分为以下几种类型:
- 数据库事务:由数据库管理系统(DBMS)管理的事务类型,保证在同一数据库实例中执行的一组操作具有ACID特性,是事务管理的基础。
- 基于单连接的 JDBC 事务:使用
java.sql.Connection
对象管理的事务,适用于单个数据库的事务操作。由于它依赖于单一的数据库连接,因此无法跨多个数据库或资源进行事务管理。 - 基于多资源管理的 JTA 事务:通过 Java Transaction API(JTA)管理的分布式事务,能够在多个资源管理器(如多个数据库、消息队列等)之间协调事务。JTA 通常用于需要跨多个资源进行一致性保证的企业级应用。
- 面向多服务的分布式事务:涉及跨多个服务或微服务的事务管理,需要确保多个独立系统或服务之间的数据一致性。实现方法包括两阶段提交(2PC)、三阶段提交(3PC)以及基于事件驱动的补偿事务模式(如Saga模式)。这种事务管理在微服务架构中尤为重要。
2.1 数据库事务
数据库事务是最基本的事务类型,由数据库管理系统(DBMS)管理。一个数据库事务(Transaction)由若干个SQL语句构成的操作序列,旨在确保数据的一致性和完整性。数据库系统保证在一个事务中的所有SQL语句要么全部成功执行,要么全部回滚,确保事务具有ACID特性(原子性、一致性、隔离性、持久性)。这也是所有事务管理的基础。
START TRANSACTION;
INSERT INTO depositor_info VALUE (1, "kern");
INSERT INTO depositor_info VALUE (2, "kern");
INSERT INTO depositor_info VALUE (3, "kern");
COMMIT;
2.1 JDBC 事务 - 单服务单连接
JDBC是 Java 中用于与数据库交互的 API,它提供了管理事务的接口,包括支持事务的ACID特性和设置事务的隔离级别。事务的隔离级别(读未提交、读已提交、可重复读、可序列化)是由 SQL 标准定义的,而 JDBC 允许开发者在Java应用中控制这些级别。
Connection conn = openConnection();
try {// 关闭自动提交:conn.setAutoCommit(false);// 执行多条SQL语句:insert(); insert(); insert();// 提交事务:conn.commit();
} catch (SQLException e) {// 回滚事务:conn.rollback();
} finally {conn.setAutoCommit(true);conn.close();
}
JDBC事务在项目中是非常常见的,常见的持久性框架都实现了JDBC事务,例如 ORM框架 Mybatis 、Hibernate等,又或者基于分布式事务JTA的各类实现方案等。
在Java中, JDBC事务的基于连接的,也就是 java.sql.Connection
对象,这个也是导致 JDBC事务无法独立完成分布式场景下的事务要求。
**总结: **
- 优势:JDBC 使用 Java 进行数据库的事务操作提供了最基本的支持。通过 JDBC 事务,我们可以将多个 SQL 语句放到同一个事务中,保证其 ACID 特性。JDBC 事务的主要优点是 API 简单易用,性能相对较好,适合单数据库的事务操作。
- 劣势:JDBC 事务只能限于一个数据库连接!这意味着在需要跨多个数据库或分布式场景的操作中,JDBC 事务就无能为力了。要在这些复杂场景中实现事务管理,通常需要借助 JTA 等分布式事务管理工具。
2.2 JTA事务 - 单服务多连接
JTA 全称 Java Transaction API,是 Java 平台中用于管理事务的标准 API,它支持单机事务与分布式事务。在 Java 环境中,JTA 允许应用程序完成跨越多个 XA 资源的分布式事务,即在两个或多个资源上访问并且更新数据。JDBC 驱动程序对 JTA 的支持极大地增强了数据访问能力。
作为 J2EE 平台规范的一部分,JTA 与 JDBC 类似,自身只提供了一组 Java 接口,需要由供应商来实现这些接口。JTA 可以使用 XA 协议来管理分布式事务,但 JTA 本身并不等同于 XA 协议的实现。
开发人员只需要调用Spring JTA接口即可实现JTA事务管理功能。JTA事务比JDBC事务更强大。一个JTA事务可以有多个参与者,而一个JDBC事务则被限定在一个单一的数据库连接。
JTA 定义了分布式事务的 5 种角色,不同角色关注不同的内容。
- **事务管理器(Transaction Manager):**是分布式事务的核心组件。在 JTA 中,通过
**TransactionManager**
接口来表示,提供了事务操作、资源管理、同步和事务传播等功能。它类似于 Spring 中的**PlatformTransactionManager**
。- 事务管理:负责管理事务的生命周期,包括事务的开始、提交、回滚等操作,并将事务对象存储在线程上下文中。
- 资源管理:管理资源和事务对象之间的关系,以便通过两阶段提交(2PC)协议来实现事务一致性。
- 同步:提供
Synchronization
回调接口,用于事务完成前后的操作。 - 事务传播:管理事务在嵌套操作中的传播行为(事务与子事务),与 Spring 的事务传播机制类似。
- **资源管理器(Resource Manager):**提供了对底层资源(如关系型数据库、消息队列)的访问能力。在 JTA 中,使用
XAResource
接口表示,通常通过资源适配器(如 JDBC 驱动)来实现。 - **通信资源管理器:**用于支持事务管理器之间在跨应用的分布式事务中的通信。在 JTA 中,这由实现 JTS 规范的组件负责,通常开发者无需直接处理。
- 应用: 使用分布式事务的应用可以通过 JTA 规范中的
UserTransaction
接口来操作事务。通常情况下,UserTransaction
需要从 **JNDI(Java Naming and Directory Interface,Java 命名和目录接口,表示一个 Java 应用程序需要获取的资源的地址)**中获取。 - 应用服务器:应用的运行环境,JTA 规定事务管理器应该由应用服务器来实现。
总结
- 优势:提供了分布式事务的解决方案,严格的ACID。
- 劣势:
- 实现复杂,通常情况下
UserTransaction
需要从 JNDI 获取,这使得使用 JTA 需要同时依赖 JTA 和 JNDI。 - 由于其采用的 2PC 和 3PC 分布式事务方案,前者为同步阻塞方案,后者即便增加了超时机制,依然采用阻塞方式,导致 JTA 只适合对一致性要求高的系统。换而言之,这可能会影响系统的可用性。
- JTA 通常只能在应用服务器环境下使用,因此会限制代码的复用性和便携性。
- 实现复杂,通常情况下
2.3 分布式事务 - 多服务多连接
2.3.1 分布式事务的定义
- 分布式事务:是指在分布式系统中,事务的各个组件(事务的发起者、参与者、数据资源服务器、事务管理器)分别位于不同的节点上。由于这些组件可能在不同的地理位置或不同的服务器上运行,因此需要通过网络通信来协调事务的一致性和完整性。
- 刚性事务和柔性事务:
- 刚性事务(又称为强一致性事务):严格遵循 ACID 特性,确保在任何情况下,所有操作都能以一致的状态完成。典型的实现方式包括两阶段提交(2PC)和三阶段提交(3PC)。刚性事务适用于那些对数据一致性要求非常高的场景,但通常伴随着较高的性能开销。
- 柔性事务(又称为弱一致性事务或最终一致性事务):允许在一定时间内数据处于不一致状态,但最终将达到一致性。柔性事务通常采用补偿事务、Saga 模式或事件驱动架构来实现,适用于对一致性要求较为灵活的场景,通常伴随着更好的性能。
2.3.2 分布式事物的类型
2.3.2.1 两阶段提交事务
2PC, Two-Phase Commit
- 定义:两阶段提交协议是最经典的分布式事务协议,用于确保分布式系统中所有参与者的一致性。2PC 包括两个阶段:准备阶段(prepare phase)和提交阶段(commit phase)。
- 流程:
- 准备阶段:协调者向所有参与者发送准备提交请求,参与者执行操作并记录状态(准备提交或回滚)。
- 提交阶段:如果所有参与者都准备提交,协调者发送提交命令,否则发送回滚命令。
- 优点:确保了严格的一致性。
- 缺点:同步阻塞协议,性能较差;在协调者故障的情况下可能导致长时间的锁定。
2.3.2.2 三阶段提交事务
3PC, Three-Phase Commit
- 定义:三阶段提交协议是对两阶段提交的改进,通过引入超时机制和预提交阶段,进一步减少了协调者失败导致的系统阻塞问题。
- 流程:
- 投票阶段:协调者向所有参与者发送准备请求。
- 预提交阶段:如果所有参与者都同意,协调者进入预提交阶段,参与者保存操作状态,协调者等待超时。
- 提交阶段:在没有超时的情况下,协调者发送提交请求;若超时,则中止事务。
- 优点:比 2PC 更加健壮,减少了协调者故障时的阻塞。
- 缺点:复杂性增加,仍然存在性能问题。
2.3.2.3 补偿事务
Compensating Transactions
- 定义:补偿事务是一种弱一致性的分布式事务模型,允许在事务的执行过程中发生部分失败,并通过执行补偿操作来回滚之前已经成功的操作,从而达到最终一致性。
- 流程:事务在多个服务中执行操作,如果某个操作失败,系统将执行补偿操作来撤销已完成的操作,从而恢复到一致状态。
- 优点:适用于长时间运行的事务和分布式系统,提供了灵活性。
- 缺点:需要为每个操作设计相应的补偿逻辑,复杂性较高。
2.3.2.4 Saga 事务
- 定义:Saga 是一种分布式事务的设计模式,适用于微服务架构。Saga 事务将全局事务分解为一系列局部事务,每个局部事务都有对应的补偿事务。
- 流程:
- 每个子事务独立提交,如果所有子事务成功,则事务完成。
- 如果某个子事务失败,则按照相反的顺序依次执行补偿事务,以撤销之前的操作。
- 优点:提供了最终一致性,适用于分布式和微服务架构,避免了长时间的锁定。
- 缺点:实现复杂性较高,补偿逻辑需要精心设计。
2.3.2.5 TCC事务
Try-Confirm/Cancel
- 定义:TCC 是一种类似 Saga 的分布式事务模型,但它通过三个步骤(Try、Confirm、Cancel)来实现资源的锁定和释放,确保事务的一致性。
- 流程:
- Try:尝试预留资源或验证操作的可行性。
- Confirm:在所有操作成功后,真正执行操作并提交事务。
- Cancel:如果某个步骤失败,则撤销操作并释放资源。
- 优点:较为灵活,可以控制资源的锁定和释放。
- 缺点:需要对资源的预留和释放做详细设计,复杂度较高。
2.3.2.6 异步确保最终一致性
- 定义:这种模型通常基于事件驱动架构(EDA),通过异步事件的方式实现分布式系统中不同节点间的数据最终一致性。
- 流程:系统各节点独立处理事务,并通过事件总线或消息队列将状态变化通知其他节点,各节点根据收到的事件进行相应处理,确保系统最终一致。
- 优点:提供了高可用性和横向扩展能力,适合大规模分布式系统。
- 缺点:数据一致性保证是最终一致性,可能存在短暂的非一致状态。
3. 分布式事务的由来和发展
3.1 分布式事务的理论
3.1.1 CAP定理
CAP定理(CAP therorem),又被称为布鲁尔定理(Brewer’s theorem),他指出在分布式系统中,不可能同时满足以下三点:
- Consistency** (一致性)**:“all nodes see the same data at the same time”,即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致,这就是分布式的一致性。一致性的问题在并发系统中不可避免,对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。
- Availability** (可用性)**: 可用性指“Reads and writes always succeed”,即服务一直可用,而且是正常响应时间。好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。
- Partition Tolerance** (分区容错性)**: 即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性或可用性的服务。分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,对于用户而言并没有什么体验上的影响。
CAP定理体现在事务上,主要表现为事务的强一致性和弱一致性。当分布式系统要求强一致性时(采用同步阻塞的分布式事务方案),则其必要需要牺牲部分可用性。当分布式系统仅要求弱一致性时,则可用性增强。分区容错性由于网络等客观因素影响,基本上时分布式系统必须具有的特性。
3.1.2 BASE理论(最终一致性理论)
BASE理论是对CAP中的一致性和可用性进行一个权衡的结果。核心思想是即使无法做到强一致性,但可以使用一些技术手段达到最终一致。
- Basically Available**(基本可用)**:允许系统发生故障时,损失一部分可用性。
- Soft state**(软状态)**:允许数据同步存在延迟。
- Eventually consistent**(最终一致性)**: 不需要保持强一致性,最终一致即可。
总结起来就是分布式系统允许牺牲一定的可用性(部分错误)以换取更高的性能,更好的体验,并且在数据的一致性问题上采用柔性策略,保证数据的最终一致。
3.1.3 柔性事务
不同于 ACID 的刚性事务,在分布式场景下基于 BASE 理论,就出现了柔性事务的概念。要想通过柔性事务来达到最终的一致性,就需要依赖于一些特性,这些特性在具体的方案中不一定都要满足,因为不同的方案要求不一样;但是都不满足的话,是不可能做柔性事务的。
3.1.3 幂等操作
幂等操作的定义
幂等性是指一个操作无论执行多少次,产生的效果都是相同的。在幂等操作中,重复执行操作不会改变系统的状态,或不会导致副作用的累积。换句话说,执行一次和执行多次的结果是相同的。
幂等操作在分布式系统中的重要性
在分布式系统中,由于网络不稳定、服务不可用等原因,可能会导致某个操作失败。为了提高系统的可靠性,常见的策略是对失败的操作进行重试。然而,如果操作不是幂等的,重试可能导致不期望的副作用,例如重复扣款、重复订单等问题。 此外 在分布式环境下,网络延迟和重放攻击(replay attack)可能会导致同一操作被多次执行。如果操作是幂等的,即使这些问题发生,也能保证系统状态的一致性。
幂等操作与分布式事务的关系
分布式事务需要确保在多个节点或服务之间的操作能够一致性提交或回滚。然而,由于网络问题、节点故障等原因,事务可能会部分成功或失败,这时需要通过重试机制来确保操作的完成。如果系统中的操作是幂等的,那么即使在分布式事务中某些操作因故障需要重试,也不会影响系统的一致性。幂等性可以显著简化分布式事务的管理,因为它消除了重复执行带来的副作用风险。在 Saga 模式(一个柔性事务模型)中,事务被拆分为一系列子事务,每个子事务都有一个补偿操作。幂等性在 Saga 模式中尤为重要,因为子事务和补偿操作可能会因为重试而多次执行。保证这些操作的幂等性可以避免由于重试带来的数据不一致问题。
3.2 分布式事务的由来
3.2.1 单节点事务
- 起源:事务的概念最早起源于数据库系统。单节点事务基于数据库连接,保证了在单一数据库中的多次操作能够同步保存和恢复,这也就是事务的原子性(Atomicity)。在这种模型下,事务的执行依赖于单个数据库实例,并能够确保在操作过程中即使发生失败,数据库也能回滚到一致的状态。
- 演化:随着软件系统从单体架构逐步复杂化并最终走向分布式架构,数据一致性的问题开始在多个数据源和服务之间变得显著。最初,开发者通过 JDBC 事务来管理单个数据库的事务操作,确保数据的一致性。然而,随着需求的演变,从单数据源的原子性事务演化出了多数据源的后置提交技术(例如 MyBatis-Plus 的动态数据源),这些技术在某种程度上模仿了事务的行为,但不是真正的分布式事务。这些技术有时被称为“伪事务”,它们最终推动了真正的分布式事务技术的发展。
3.2.2 分布式事务
3.2.2.1 刚性分布式事务
- XA 模型的引入:
- 1994年:X/Open 组织首次定义了分布式事务处理模型,即 XA 模型。该模型分为四个部分:应用程序(Application Program)、事务管理器(Transaction Manager)、资源管理器(Resource Manager)和通信资源管理器(Communication Resource Manager)。
- 组件描述:在典型的 XA 模型中,事务管理器通常由交易中间件实现,负责协调分布式事务的执行;资源管理器(如数据库)提供数据存储服务,并与事务管理器协同工作;通信资源管理器(通常是消息中间件)用于在分布式环境中传递事务信息。
- 两阶段提交(2PC):XA 模型引入了两阶段提交协议(2PC),这是一个严格一致性的分布式事务方案,确保了分布式系统中所有参与者的一致性。
- 三阶段提交(3PC):为了解决 2PC 中的一些缺陷(如事务管理器发生脑裂时的数据不一致问题),后续提出了三阶段提交协议(3PC),进一步增强了系统的可靠性和容错能力。
3.2.2.2 柔性分布式事务
- 补偿式事务(TCC 模式):
- 2007年:Pat Helland 发表了一篇名为《Life beyond Distributed Transactions: an Apostate’s Opinion》的论文,首次提出了 TCC(Try-Confirm/Cancel)理论。TCC 是一种补偿式事务模型,其核心思想是通过预留资源(提供中间状态,如冻结账户金额)来尽早释放资源的锁定。
- TCC 模式:在 TCC 模式中,如果事务可以提交,则确认(Confirm)预留的资源;如果事务要回滚,则取消(Cancel)预留的资源。虽然 TCC 能有效避免长时间的资源锁定,但也意味着需要编写大量的补偿代码,并且对业务逻辑有较强的侵入性。
- 通知式事务(基于 BASE 理论):
- BASE 理论:通知式事务的概念源自 eBay 的架构师提出的 BASE 理论。BASE 理论认为,在大型分布式系统中可以牺牲部分可用性和数据的强一致性,转而追求数据的最终一致性。
- 通知式事务:基于 BASE 理论,通知式事务应运而生,主要包括事务消息和最大努力通知型分布式事务两个组成部分。通知式事务的核心思想是通过消息队列(MQ)来通知其他事务参与者事务的执行状态。
- 异步事务:由于引入了 MQ 组件,各个事务参与者可以异步执行,互不依赖,确保系统的松耦合性。因此,通知式事务也被称为异步事务。这种模式在提升系统可用性的同时,保证了数据的一致性在最终时刻达到。
3.2.3 分布式事务的最新演化 - 补充知识,了解即可
事件驱动架构与最终一致性
- 事件溯源(Event Sourcing):
- 定义:事件溯源是一种设计模式,它将状态变化记录为一系列事件,系统的状态是这些事件的投影。通过事件驱动的方式,可以在分布式系统中实现最终一致性,而不需要传统的强一致性事务模型。
- 应用场景:在复杂的分布式系统中,尤其是涉及多个微服务时,事件溯源可以帮助跟踪和管理系统中每个操作的历史记录,使得系统具备很强的可追溯性和灵活性。
- 优势:这种模式下,系统通过处理一系列事件,逐步达到一致性。即使某些服务暂时不可用,事件可以稍后重播,以确保系统最终达到一致的状态。
- CQRS(Command Query Responsibility Segregation)模式:
- 定义:CQRS 模式将数据的命令操作(写操作)与查询操作(读操作)分离,使用不同的模型处理。这种模式通常结合事件溯源使用,增强系统的可扩展性和性能。
- 优势:在分布式系统中,CQRS 模式能够有效分离操作的责任,并通过事件驱动的方式实现数据的最终一致性,适合高并发、高可用的场景。
- 典型应用:在电商平台中,订单的处理和查询通常采用 CQRS 模式,确保订单操作的高效性和数据的一致性。
分布式协调与共识算法
- Paxos 和 Raft 共识算法:
- Paxos:Paxos 是一种分布式一致性算法,广泛应用于分布式系统中,确保多个节点在面对网络分区和故障时仍能达成一致。尽管 Paxos 理论上能够实现分布式事务,但其复杂性限制了实际应用。
- Raft:Raft 是一种相对简单的共识算法,设计目的是比 Paxos 更容易理解和实现。Raft 在分布式系统中的应用包括分布式日志、状态机复制等,确保多个节点的状态一致性。
- 应用场景:
- 分布式数据库:现代分布式数据库(如 Google Spanner、CockroachDB)使用 Paxos 或 Raft 算法来确保分布式事务的一致性和高可用性。这些算法使得分布式数据库能够在全球范围内分布式部署,提供跨数据中心的强一致性保证。
- 分布式锁服务:如 etcd、ZooKeeper 等工具使用共识算法来实现分布式锁服务,为分布式事务提供基础设施支持,确保多个服务实例间的协调和一致性。
混合事务处理与分析(HTAP)
- HTAP 的引入:
- 定义:HTAP(Hybrid Transactional and Analytical Processing)是一种新兴的数据库架构,能够同时支持事务处理(OLTP)和分析处理(OLAP)。这种架构使得企业能够在同一系统中同时执行事务和分析操作,而不必将它们分开在不同的系统中处理。
- 技术实现:HTAP 系统通常依赖于内存计算、列式存储和分布式计算框架,以便在处理高并发事务的同时,支持复杂的分析查询。
- 应用场景:在需要实时决策的应用场景中,HTAP 系统能够大大提高效率。例如,电商平台可以在用户下单的同时,实时分析用户行为数据,从而做出个性化推荐。
- 与分布式事务的关系:
- HTAP 系统需要处理同时发生的大量事务操作,并在多个节点间保持数据的一致性,这通常依赖分布式事务处理机制。为了保证系统的可扩展性和一致性,HTAP 系统可能会结合使用柔性分布式事务(如 BASE 理论)和共识算法(如 Raft)来实现。
云原生环境中的分布式事务
- Kubernetes 和微服务框架的支持:
- 云原生架构:随着 Kubernetes 等云原生平台的普及,分布式系统的部署和管理变得更加动态和弹性。这带来了新的分布式事务挑战,如服务的动态扩展、网络分区处理等。
- Service Mesh:Service Mesh(如 Istio、Linkerd)通过在微服务之间提供透明的网络层管理,支持分布式追踪、流量控制和安全策略。这些功能可以帮助协调微服务间的分布式事务处理,确保跨服务的请求可以被追踪和管理。
- Serverless 与 FaaS:
- 无服务器架构:在 Serverless 架构中,函数即服务(FaaS)使得应用可以按需扩展和执行。由于函数的无状态性,分布式事务管理变得更具挑战性,通常需要结合事件驱动架构和幂等性设计来处理。
- 分布式事务管理:在无服务器环境中,分布式事务通常依赖于事件总线(如 AWS Lambda 的 Step Functions)和外部状态管理(如 DynamoDB 的事务支持)来实现,确保在无状态函数的执行过程中能够保持一致性和可靠性。
3.3 分布式事务的常用方案
3.3.1 2PC 两阶段提交方案/XA方案
2PC全称Two-phaseCommit,中文名是二阶段提交,是XA规范的实现思路。
什么是XA规范?
XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。
XA 规范描述了全局的事务管理器与局部的资源管理器之间的接口。 XA规范的目的是允许的多个资源(如数据库,应用服务器,消息队列等)在同一事务中访问,这样可以使 ACID 属性跨越应用程序而保持有效。
XA 规范使用两阶段提交来保证所有资源同时提交或回滚任何特定的事务。
XA 规范在上世纪 90 年代初就被提出。目前,几乎所有主流的数据库都对 XA 规范提供了支持。
XA 事务的基础是两阶段提交协议。需要有一个事务协调者来保证所有的事务参与者都完成了准备工作(第一阶段)。如果协调者收到所有参与者都准备好的消息,就会通知所有的事务都可以提交了(第二阶段)。数据库在这个 XA 事务中扮演的是参与者的角色,而不是协调者(事务管理器)。
XA规范模型包括应用程序( AP )、事务管理器( TM )、资源管理器( RM )、通信资源管理器( CRM )四部分。一般,常见的事务管理器( TM )是交易中间件,常见的资源管理器( RM )是数据库,常见的通信资源管理器( CRM )是消息中间件。
2PC 通常使用到XA中的三个角色TM、AP、RM
- AP:事务发起方,通常为微服务自身;定义事务边界(事务开始、结束),并访问事务边界内的资源
- TM:事务协调方,事务操作总控;管理事务全局事务,分配事务唯一标识,监控事务的执行进度,负责事务的提交、回滚、失败恢复。
- RM:本地事务资源,根据协调方命令进行操作;管理本地共享资源(既数据库)。
2PC 分成2个阶段,第一阶段:请求阶段(commit-request phase,或称表决阶段,voting phase)和第二阶段:提交阶段(commit phase)。
- 表决阶段:事务协调者™串行给每个参与者(RM)发送Prepare消息,每个参与者要么直接返回失败,要么在本地SQL执行、记录事务日志(Undo、Redo),但不提交,到达一种“万事俱备,只欠东风”的状态。可以进一步将准备阶段分为以下三个步骤:
- 1)TM串行向每个参与者节点询问是否可以执行提交操作,并等待各参与者节点的响应。
- 2)参与者节点执行询问的所有SQL语句,并将Undo(旧数据备份)和Redo(新数据备份)写入日志。
- 3)各参与者节点响应TM发起的询问。如果参与者节点的事务操作实际执行成功,则返回一个”success”消息;如果参与者节点的事务操作实际执行失败,则返回一个”abort”消息。
- 提交阶段:如果TM收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据TM的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)。
当TM从所有参与者节点获得的相应消息都为”success”时:
- <font style="color:rgb(18, 18, 18);">1)TM向所有参与者节点发出”正式提交(commit)”的请求。</font>
- <font style="color:rgb(18, 18, 18);">2)参与者节点正式完成操作,并释放在整个事务期间内占用的资源。</font>
- <font style="color:rgb(18, 18, 18);">3)参与者节点向TM发送”完成”消息。</font>
- <font style="color:rgb(18, 18, 18);">4)TM受到所有参与者节点反馈的”完成”消息后,完成事务。</font>
如果任一参与者节点在第一阶段返回的响应消息为”abort”,或者 TM在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:
- <font style="color:rgb(18, 18, 18);">1)TM向所有参与者节点发出”回滚操作(rollback)”的请求。</font>
- <font style="color:rgb(18, 18, 18);">2)参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。</font>
- <font style="color:rgb(18, 18, 18);">3)参与者节点向TM发送”回滚完成”消息。</font>
- <font style="color:rgb(18, 18, 18);">4)TM受到所有参与者节点反馈的”回滚完成”消息后,取消事务。</font>
不管最后结果如何,第二阶段都会结束当前事务。
该方案的缺陷:
- 全流程的同步阻塞:不管是第一阶段还是第二阶段,所有参与节点都是事务阻塞型。当参与者占有公共资源时,其他第三方访问公共资源可能不得不处于阻塞状态。
- TM单点故障:由于全流程依赖TM的协调,一旦TM发生故障。参与者会一直阻塞下去。尤其在第二阶段,TM发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。所有参与者必须等待TM重新上线(TM重新选举)后才能继续工作。
- TM脑裂引起数据不一致:在第二阶段中,当TM向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中TM发生了故障,这会导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据不一致性的现象。
- TM脑裂引起事务状态不确定:TM再发出commit消息之后宕机,而接收到这条消息的参与者同时也宕机了。那么即使通过选举协议产生了新的TM,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
基于以上的缺陷,3PC应运而生。
3.3.2 3PC 三阶段提交方案
2PC是CP的刚性事务,追求数据强一致性。但是通过我们上面分析可以得知TM脑裂可能造成数据不一致和事务状态不确定问题。无法达到CP的完美状态。因此业界就出现了3PC,用来处理TM脑裂引起的数据不一致和事务状态不确定问题。因为TM、RM双向超时机制,所以维基百科对3PC定义为“非阻塞”协议。
3PC 分成3个阶段:CanCommit(准备阶段)、PreCommit(对齐阶段)、DoCommit(提交阶段);笔者根据资料对3阶段进行比较合适的翻译,非官方翻译。
- 准备阶段:跟2PC的表决阶段很类似,TM向参与者发送commit请求,参与者如果可以提交就返回Yes,否则返回No,询问超时默认参与者为No。唯一差别在于SQL层面:准备阶段只做了SQL处理,并未记录事务日志(Undo 和Redo)
- 对齐阶段:TM 和 各个参与者对齐事务状态,TM 通知各个参与者事务最终状态,各个参与者如果一直未收到事务对齐通知,会在超时后从TM反查事务状态实现事务状态对齐。在SQL层面:事务状态对齐后,记录事务日志(Undo 和Redo)
- 提交阶段:该阶段进行真正的事务提交。根据第二阶段得到的事务状态结果,各参与者根据TM的通知命令进行提交/abort或者超时后自动提交/abort。
3PC是将2PC的提交阶段拆分成预提交和确认提交两个阶段,并引入超时机制,实际上,只要任一节点存活并处于preCommit或者Commit状态,其他节点都可以完成事务提交。3PC一定程度解决了2PC的数据不一致和事务状态不确定问题。
3PC 虽然解决了TM与参与者都异常情况下导致数据不一致的问题,3PC 依然带来其他问题:比如,网络分区问题,在 preCommit 消息发送后突然两个机房断开,这时候 TM 所在机房会 abort, 另外剩余参与者的机房则会 commit。而且由于3PC 的设计过于复杂,在解决2PC 问题的同时也引入了新的问题,所以在实际上应用不是很广泛。
3.3.3 TCC方案
TCC方案分为Try Confirm Cancel三个阶段,属于补偿性分布式事务。
- Try:尝试待执行的业务:这个过程并未执行业务,只是完成所有业务的一致性检查,并预留好执行所需的全部资源
- Confirm:执行业务:这个过程真正开始执行业务,由于Try阶段已经完成了一致性检查,因此本过程直接执行,而不做任何检查。并且在执行的过程中,会使用到Try阶段预留的业务资源。
- Cancel:取消执行的业务:若业务执行失败,则进入Cancel阶段,它会释放所有占用的业务资源,并回滚Confirm阶段执行的操作。
以一个电商系统用户购买商品的流水线为例。
Try阶段:
在Try阶段成功之后进入Confirm阶段,如有任何异常,进入Cancel阶段。
Confirm阶段
Cancel阶段
假设库存扣减失败,此时需要回滚取消事务。
TCC方案适用于一致性要求极高的系统中,比如金钱交易相关的系统中,不过可以看出,其基于补偿的原理,因此,需要编写大量的补偿事务的代码,比较冗余。
现有开源的TCC框架:TCC-transaction。
3.3.4 本地消息表
需要注意的是,该方案中,在A系统中,我们首先写入业务表,然后写入消息表,然后将消息发送到MQ中,在B系统中需要先写入消息表,这是为了保证消息重复消费,为了保证消息消费的幂等性,我们可以使用数据的唯一键来约束。
当B系统执行成功之后,需要通知A系统执行成功,此时可以使用一个监听器,如Zookeeper,ZK监听到执行成功更新A系统成功。然后开始发送下一条消息。
A系统中需要有一个后台线程,不断的去判断A系统的状态为待确认的消息,设置超时机制,如果超时,重新发送到MQ中。直到执行成功。
可以看出,本地消息表方案需要写入消息表中,如果在高并发的场景下会进行大量的磁盘IO,因此该方案不适用于高并发场景。
3.3.5 可靠消息最终一致性方案
该方案基于本地消息表进行优化,不使用本地消息表,而是基于MQ,比如阿里的RocketMQ就支持消息事务。
在该方案中,首先A系统需要向MQ中发送prepare消息,然后执行A系统的业务,写入数据库成功之后向MQ发送confirm消息,当消息为confirm状态时,B系统就可以消费到消息,消费成功之后返回ACK确认消息给MQ。
需要注意的是。需要保证B系统消费消息的幂等性,可以借助第三方系统。比如在redis中设置标识,标明已经消费过该消息,或者借助ZK基于分布式锁的原理,创建节点,重复消费消息,创建失败。
3.3.6 最大努力通知方案
最大努力通知型( Best-effort delivery)是最简单的一种柔性事务,适用于一些最终一致性时间敏感度低的业务,且被动方处理结果不影响主动方的处理结果。典型的使用场景:如银行通知、商户通知等。
在该系统中,A系统执行完本地事务,向MQ发送消息,最大努力通知服务消费消息,比如消息服务,然后调用B系统的接口,执行B系统的本地事务,如果B系统执行成功则OK,否则不断重试,重复多次之后还是失败的话就放弃执行。
3.4 分布式事务的主流技术架构与中间件
3.4.1 分布式事务的主流技术架构
以下是分布式事务的几种主要技术架构:
- 两阶段提交协议(2PC):这是最经典的分布式事务解决方案,通过准备阶段和提交阶段来协调事务,但在网络分区或节点故障时可能导致阻塞问题。
- 三阶段提交协议(3PC):对2PC进行改进,通过增加准备阶段的超时机制来减少阻塞的可能性,但仍然存在复杂性高的问题。
- 补偿事务(TCC,Try-Confirm/Cancel):分为Try、Confirm、Cancel三个阶段。首先尝试执行(Try),然后确认(Confirm)或取消(Cancel)。适合需要在多个系统之间协作的场景。
- 最终一致性:通过异步操作来保证数据的一致性,而不是在所有操作立即完成时保证一致性。常见的实现方式包括消息队列和事件溯源。
- Saga模式:将事务拆分为一系列小事务,每个小事务完成后触发下一个事务,失败时按顺序补偿之前的小事务操作。Saga适合长事务且允许部分数据不一致的场景。
3.4.2 主流分布式事务中间件
以下是一些支持分布式事务的主流中间件:
- Atomikos:支持2PC和TCC,主要用于JTA事务的分布式事务管理。
- Seata:阿里巴巴开源的分布式事务解决方案,支持AT模式、TCC、Saga和XA事务。
- Apache Camel:支持TCC、Saga和补偿事务,通过集成各种消息队列、数据库和服务,实现分布式事务。
- Narayana:Red Hat提供的分布式事务管理器,支持XA事务、Saga和TCC,常用于与WildFly等应用服务器结合使用。
以下是主要中间件对不同事务方案的支持情况的比较表格:
中间件 | 2PC | 3PC | TCC | Saga (长事务) | 最终一致性 |
---|---|---|---|---|---|
Atomikos | 支持 | 不支持 | 支持 | 不支持 | 不支持 |
Seata | 支持 | 不支持 | 支持 | 支持 | 支持 |
Apache Camel | 不支持 | 不支持 | 支持 | 支持 | 支持 |
Narayana | 支持 | 不支持 | 支持 | 支持 | 不支持 |
附录
软件事务存储技术 - Actor模型(无共享变量模型)
https://blog.csdn.net/danpu0978/article/details/106776921
https://www.javacodegeeks.com/2013/01/software-transactional-memory-stm.html
https://blog.csdn.net/qq_34446716/article/details/120261171
软件事务存储技术(software transactional memory)是2018年公布的计算机科学技术名词。定义自《计算机科学技术名词 》第三版。
<!-- 配置akka依赖-->
<dependency><groupId>com.typesafe.akka</groupId><artifactId>akka-actor_2.11</artifactId><version>2.3.11</version>
</dependency>
参考资源:
https://zhuanlan.zhihu.com/p/144522566
https://zhuanlan.zhihu.com/p/403569153
https://zhuanlan.zhihu.com/p/183753774
https://blog.51cto.com/u_15287666/3020674?b=totalstatistic#31__33
https://zhuanlan.zhihu.com/p/268441621
https://blog.51cto.com/u_15287666/3020674?b=totalstatistic#31__33
https://zhuanlan.zhihu.com/p/543462686
https://zhuanlan.zhihu.com/p/145764453
https://www.163.com/dy/article/HBPI73EN0511CUMI.html