分布式事务
1. 事务
- 事务是恢复和并发控制的基本单位,事务有四个特性(ACID),原子性(Atomicity),一致性(Consistency),隔离性(Isolation),持久性(Durability)。
2. 事务经典场景
A 给 B 转账 100,流程步骤如下:
1、A 减 100
2、B 多 100。
如果第一步骤执行后,系统崩溃掉了。
问题: A 被减掉了 100,但 B 的钱未能加 100。 此时,A + B 的金钱总额凭空少了 100。数据不一致了。
解决思路: 将步骤 1 和步骤 2 能够绑定在一起执行,不可分;并且在步骤 1 和步骤 2 执行的过程中,尽量规避中间状态。
3. 事务与锁
3.1 锁的问题场景
- 多对一问题,多个操作者同时操作一个资源,而资源的状态变化是非原子的(有中间态), 哄抢会导致资源状态混乱。
3.2 事务的问题场景
- 一对多的问题,一个操作者需要绑定操作一系列资源(比如多条 sql),若任何一条操作失败, 都会导致整个操作失去意义。
4. 事务的实现方式
- 编程式事务:使用 TransactionTemplate 或者直接使用底层的 TransactionManager 来操作事务 commit 或者 rollback。
- 声明式事务:建立在 AOP 基础上,通过对方法前后进行拦截,加入编程式事务里的流程控制逻辑。使用的时候只需要在方法前面加上@Transactional 注解。
5. 分布式事务
5.1 产生的原因
- 事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用。在这种环境中,我们之前说过数据库的 ACID 四大特性,已经无法满足我们分布式事务。
- 本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
5.2 CAP 理论
- CAP 定理,又被叫作布鲁尔定理。
- CAP 指的是:一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。
- CAP 定律说的是,在一个分布式系统中,最多只能满足 C、A、P 中的两个,不可能三个同时满足。
- 而在分布式系统中,网络无法 100% 可靠,分区其实是一个必然现象。
- 如果我们选择了 CA 而放弃了 P,那么当发生分区现象时,为了保证一致性,这个时候必须拒绝请求,但是 A 又不允许,所以分布式系统理论上,不可能选择 CA 架构,只能选择 CP 或者 AP 架构。
- 任何横向扩展策略都要依赖于数据分区。因此,设计人员必须在一致性与可用性之间做出选择。
5.3 BASE 理论
- 往往在分布式系统中无法实现完全一致性,于是有了 BASE 理论,它是对 CAP 定律的进一步扩充。
1、Basically Available(基本可用) : 分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用;
2、Soft state(软状态) : 允许系统中存在中间状态,这个状态不影响系统可用性;
3、Eventually consistent(最终一致性) : 经过一段时间后,所有节点数据都将会达到一致。 - BASE 理论是对 CAP 中的一致性和可用性进行一个权衡的结果;
- BASE 理论核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性;
- BASE 和 ACID 是相反的,它完全不同于 ACID 的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时 间内是不一致的,但最终达到一致状态。
6. 基于 XA 协议的两阶段提交
- XA是由X/Open组织提出的分布式事务的规范。
- XA规范主要定义了(全局)事务管理器(TM)和(局部)资源管理器(RM)之间的接口。主流的关系型数据库产品都是实现了XA接口的。
- XA接口是双向的系统接口,在事务管理器(TM)以及一个或多个资源管理器(RM)之间形成通信桥梁。
- XA之所以需要引入事务管理器是因为,在分布式系统中,从理论上讲两台机器理论上无法达成一致的状态,需要引入一个单点进行协调。
- 由全局事务管理器管理和协调的事务,可以跨越多个资源(如数据库或JMS队列)和进程。全局事务管理器一般使用XA二阶段提交协议与数据库进行交互,分准备与提交两个阶段。
7. 两阶段提交(Two Phase Commit) — 2PC
- 在 XA 协议中事务分为两阶段:
1、事务管理器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交。
2、事务协调器要求每个数据库提交数据,或者回滚数据。
优点:
- 尽量保证了数据的强一致,实现成本较低,在各大主流数据库都有自己实现,对于 MySQL 是从 5.5 开始支持。
缺点:
- 单点问题:事务管理器在整个流程中扮演的角色很关键,如果其宕机,比如在第一阶段已经完成,在第二阶段正准备提交的时候事务管理器宕机,资源管理器就会一直阻塞,导致数据库无法使用。
- 同步阻塞:在准备就绪之后,资源管理器中的资源一直处于阻塞,直到提交完成,释放资源。
- 数据不一致:两阶段提交协议虽然为分布式数据强一致性所设计,但仍然存在数据不一致性的可能。 比如在第二阶段中,假设协调者发出了事务 Commit 的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行 了 Commit 操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。
两阶段提交方案锁定资源时间长,对性能影响很大,基本不适合解决微服务事务问题。
8. 三阶段提交协议— 3PC
- 2PC 的改进版,其将 2PC 的 “提交事务请求”过程一分为二。
8.1 阶段一 CanCommit
- 事务询问:协调者向所有的参与者发送一个包含事务内容的 canCommit 请求,询问是否可以执行事务提交操作,并开始等待各参与者的响应。
- 各参与者向协调者反馈事务询问的响应:参与者接收来自协调者的 canCommit 请求,如果参与者认为自己可以顺利执行事务,就返回 Yes,否则反馈 No 响应。
- 这一阶段主要是确定分布式事务的参与者是否具备了完成 commit 的条件,并不会执行事务操作。
8.2 阶段二 precommit
协调者在得到所有参与者的响应之后,会根据结果执行 2 种操作:执行事务预提交,或者中断事务
- 执行事务预提交分为 3 个步骤:
- 发送预提交请求:协调者向所有参与者节点发出 preCommit 的请求,并进入 prepared 状态。
- 事务预提交:参与者受到 preCommit 请求后,会执行事务操作,对应 2PC 中的 “执行事务”,也会 Undo 和 Redo 信息记录到事务日志中。
- 各参与者向协调者反馈事务执行的结果:如果参与者成功执行了事务,就反馈 Ack 响应,同时等待指令:提交(commit) 或终止(abort)。
- 中断事务也分为 2 个步骤:
- 发送中断请求:协调者向所有参与者节点发出 abort 请求 。
- 中断事务:参与者如果收到 abort 请求或者超时了,都会中断事务。
8.3 阶段三 docommit
- 执行提交
- 发送提交请求:进入这一阶段,如果协调者正常工作,并且接收到了所有协调者的 Ack 响应,那么协调者将从 “预提交” 状态变为 “提交” 状态,并向所有的参与者发送 doCommit 请求 。
- 事务提交:参与者收到 doCommit 请求后,会正式执行事务提交操作,并在完成之后释放在整个事务执行期间占用的事务资源。
- 反馈事务提交结果:参与者完成事务提交后,向协调者发送 Ack 消息。
- 完成事务:协调者接收到所有参与者反馈的 Ack 消息后,完成事务。
- 中断事务(假设有任何参与者反馈了 no 响应,或者超时了,就中断事务)。
- 发送中断请求:协调者向所有的参与者节点发送 abort 请求。
- 事务回滚:参与者接收到 abort 请求后,会利用其在二阶段记录的 undo 信息来执行事务回滚操作,并在完成回滚之后释放整个事务执行期间占用的资源。
- 反馈事务回滚结果:参与者在完成事务回滚之后,想协调者发送 Ack 消息。
- 中断事务:协调者接收到所有的 Ack 消息后,中断事务。
8.4 与 2pc 的区别
在阶段三,可能会出现 2 种故障:
1、协调者出现问题
2、协调者和参与者之间的网络故障
出现任一一种情况,最终都会导致参与者无法收到 doCommit 请求或者 abort 请求, 针对这种情况,参与者都会在等待超时之后,继续进行事务提交。
优点:
相比较 2PC,最大的优点是减少了参与者的阻塞范围(第一个阶段是不阻塞的),并且能够在单点故障后继续达成一致(2PC 在提交阶段会出现此问题,而 3PC 会根据协调者的状态进行回滚或者提交)。
缺点:
如果参与者收到了 preCommit 消息后,出现了网络分区,那么参与者等待超时后,都会进行事务的提交,这必然会出现事务不一致的问题。
9. TCC 方案
- TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其业务逻辑对应的确认和补偿(撤销)操作。
- 其将整个业务逻辑的每个分支显式的分成了 Try、Confirm、Cancel 三个操作。Try 部分完成业务的准备工作,confirm 部分完成业务的提交,cancel 部分完成事务的回滚。
- 优点: 跟 2PC 比起来,实现以及流程相对简单了一些,但数据的一致性比 2PC 也要差一些
- 缺点: TCC 属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,而且补偿的时候也有可能失败,在一些场景中,一些 业务流程可能用 TCC 不太好定义及处理。
10. MQ(事务消息)
目前,仅阿里云的 RocketMQ 支持事务消息。帮助用户实现类似 X/Open XA 的分布事务功能,通过 MQ 事务消息能达到分布式事务的最终一致。
1、发送方向 MQ 服务端发送消息 ;
2、 MQ Server 将消息持久化成功之后,向发送方 ACK 确认消息已经发送成功,此时消息为半消息 ;
3、发送方开始执行本地事务逻辑 ;
4、发送方根据本地事务执行结果向 MQ Server 提交二次确认(Commit 或是 Rollback),MQ Server 收到 Commit 状态则将半消息标记 为可投递,订阅方最终将收到该消息;MQ Server 收到 Rollback 状态则删除半消息,订阅方将不会接受该消息;
5、在断网或者是应用重启的特殊情况下,上述步骤 4 提交的二次确认最终未到达 MQ Server,经过固定时间后 MQ Server 将对该消息发 起消息回查;
6、发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果 ;
7、发送方根据检查得到的本地事务的最终状态再次提交二次确认,MQ Server 仍按照步骤 4 对半消息进行操作。
其中,事务消息发送对应步骤 1、2、3、4,事务消息回查对应步骤 5、6、7
11. Lcn 事务
- 锁定事务单元(lock)
- 确认事务模块状态(confirm)
- 通知事务(notify)
- LCN 并不生产事务,LCN 只是本地事务的协调工 ;
- TX-LCN 定位于一款事务协调性框架,框架其本身并不操作事务,而是基于对事务的协调从而达到事务一致性的效果。
事务控制原理
- TX-LCN 由两大模块组成,TxClient、TxManager
- TxClient 作为模块的依赖框架,提供 TX-LCN 的标准支持;TxManager 作为分布式事务的控制方。
- 事务发起方或者参与方都由 TxClient 端来控制。
1、创建事务组 是指在事务发起方开始执行业务代码之前先调用 TxManager 创建事务组对象,然后拿到事务标示 GroupId 的过程。
2、加入事务组 添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息通知给 TxManager 的操作。
3、通知事务组 是指在发起方执行完业务代码以后,将发起方执行结果状态通知给 TxManager,TxManager 将根据事务最终状态和事务组的信息 来通知相应的参与模块提交或回滚事务,并返回结果给事务发起方。