1. 简介
微服务是由许多较小的服务组成的分布式系统,它们共同提供整体应用功能。尽管这种架构风格俱有很多有点,但也存在一些缺点,其中最大的问题之一就是如何管理涉及多个服务的事务。
在分布式事务场景中,尽管事务涉及多个服务,但始终要保持ACID(原子性、一致性、隔离性和持久性)的特性。第二个困难是控制事务的隔离级别,它决定了在其他服务同时访问相同数据时,事务中可见度数据量。
分布式事务:在分布式系统中一次操作需要多个服务协同完成,这种由不同的服务之间通过网络协同完成的事务称为分布式事务
2. 2PC
2PC(Two Phase Commitment Protocol
)是一种常用的实现分布式事务的方法。它包括一个协调器组件负责控制事务并包含事务逻辑,以及其他参与节点执行其本地事务。
2PC将整个事务流程分为两个阶段:准备阶段(Prepare phase)、提交阶段(Commit phase);
2.1 准备阶段
准备阶段(Prepare phase):由协调器给每个参与者发送Prepare
消息,每个数据库参与者在本地执行事务,此时事务没有提交
协调器项所有参与者发送事务内容,询问是否可以提交事务,并等待答复
各参与者执行本地事务操作,将
Undo Log/Redo Log
记入事务日志中,但不提交事务如果参与者执行成功,给协调器反馈统一,否则反馈终止,表示事务不可以执行
2.2 提交阶段
提交阶段(Commit phase): 协调器收到参与者的失败或者超时消息后,直接给每个参与者发送回滚(Rollback)消息,否则,发送提交(commit)消息;参与者根据协调器的指令执行提交或者回滚操作
事务提交
协调器项所有参与者节点发出正式提交的commit 请求
收到协调器的commit请求后,参与者正式执行事务提交操作,并释放整个事务期间占用的资源
参与者完成事务提交后,项协调器发送ACK消息
协调器收到所有参与者节点反馈的ACK消息后,完成事务
事务回滚: 如果任意一个参与者节点在第一阶段返回的消息为终止,或者协调器节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息,那么这个事务会被回滚
协调器项所有参与者发出rollback回滚操作的请求
参与者利用准备阶段写入的Undo信息执行回滚,并释放在整个事务期间占用的资源
参与者在完成事务回滚之后,项协调器发送回滚完成的ACK消息
协调器收到所有参与者反馈的ACK消息后,取消事务
2.3 2PC的缺点
二阶段提交缺失能够提供原子型的操作,但不幸的是,二阶段提交还是有几个缺点的:
- 性能问题:执行过程中,所有参与节点都是事务阻塞的,当参与者占用公共资源时,其他第三方节点访问公共资源就不能不处于阻塞状态,为了数据一致性而牺牲了可用性,对性能影响较大,不适合高并发性能场景。
- 可靠性问题: 2PC非常依赖协调者,当协调者发生故障时,尤其是第二阶段,那么所有的参与者就会处于锁定事务资源的状态中,而无法继续完成事务操作(如果协调者挂掉,可以重新选择一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞的问题)
- 数据一致性问题:在第二阶段中,当协调者项参与者发送commit请求后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这会导致只有一部分参与者接收到了commit请求,而在这部分参与者接到commit请求后就会执行commit操作,但是其他的参与者未接收到commit请求,于是整个分布式系统出现了数据一致性现象
- 二阶段无法解决的问题:协调者在发出commit消息之后宕机,而唯一接收到消息的参与者同时也发生宕机,那么即使协调者通过选择协议产生新的协调者,这条事务的状态也是不确定的,没有人知道事务是否被提交
3. 3PC
3PC三阶段提交协议,是二阶段提交协议的改进版本,三阶段提交有两个改动点:
在协调者和参与者都引入超时机制
在第一阶段和第二阶段中插入一个准备阶段,保证了在最后提交阶段之前各参与节点的状态时一致的
举个栗子:上课考试前,老师开始点名,点到的同学都会应一声”到”(CanCommit 阶段),老师开始发放试卷,同学们开始答题,答题完了之后举手表示已经做完等待老师收卷子(PreCommit阶段),下课后,老师说统一交卷,同学们听到后,陆续把试卷交给老师,并收拾好文具(doCommit阶段)
3PC会分为3个阶段:CanCommit准备阶段、PreCommit预提交阶段、DoCommit提交阶段,处理流程如下:
3.1 CanCommit 准备阶段
协调者项参与者发送CanCommit
请求,参与者如果可以提交就返回YES
反之则返回NO,具体流程如下:
事务询问:协调者项所有参与者发送包含事务内容的
CanCommit
请求,询问是否可以提交事务,并等待所有参与者答复响应反馈:参与者收到
CanCommit
请求后,入股认为可以执行事务操作,则反馈Yes并进入预备状态,否则反馈NO
3.2 PreCommit 阶段
协调者根据参与者的反馈情况来决定是否可以进行事务的PreCommit操作。根据响应情况,有以下两种可能:
执行事务:假如所有参与者都反馈YES,协调者预执行事务,具体如下:
- 发送预提交请求:协调者向参与者发送PreCommit请求,并进入准备阶段
- 事务预提交:参与者收到PreCommit请求后,会执行本地事务操作,并将Undo和Redo信息记录到事务日志中(但不提交事务)
- 响应反馈:如果参与者成功的执行了事务操作,则返回ACK响应,同时等待最终命令
中断事务:假如有任何一个参与者项协调者发送了NO响应,或者等待超时之后,协调者都没有接收到参与者的响应,那么就执行事务的中断,流程如下:
- 发送中断请求:协调者向所有参与者发送
abort
请求
- 中断事务:参与者接收到协调者的
abort
请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断
- 发送中断请求:协调者向所有参与者发送
3.3 DoCommit 阶段
该阶段进行真正的事务提交,也可以分为两种情况:
提交事务:
发送提交请求:协调者收到所有参与者发送的ACK响应,从预提交状态进入提交状态,冰箱所有参与者发送doCommit请求
本地事务提交:参与者接收到doCommit请求之后,执行正式的事务提交,并在完成事务提交之后释放所有资源
响应反馈:事务提交完成之后,向协调者发送ack响应
完成事务:协调者接收到所有参与者的ack响应之后,完成事务
中断事务: 任何一个参与者反馈NO,或者等待超时后协调者无法收到所有参与者的反馈,即中断事务
发送中断请求:如果协调者处于工作状态,向所有参与者发出abort请求
事务回滚:参与者接收到abort请求之后,利用器在阶段二记录的Undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的资源
反馈结果:参与者完成事务回滚之后,向协调者反馈ACK消息
中断事务:协调者接收到参与者反馈的ACK消息之后,执行事务的中断
进入doCommit
阶段后,无论协调者出现问题,或者协调者与参与者之间的网络出现问题,都会导致参与者无法接收到协调者发出的doCommit
请求或abort
请求。此时,参与者都会在等待超时之后,继续执行事务挑。
这其实基于概率来决定的,当进入第三阶段时,说明第一阶段收到所有参与者的CanCommit
响应都是YES
,意味着大家都同意了,并且第二阶段所有的参与者对协调者的PreCommit
请求也是同意的。所以,当进入第三阶段时,由于网络超时等原因,虽然参与者没有收到commit或者abort请求,但是参与者相信成功提交的概率更大
3.3 3PC的优缺点
与2PC相比,3PC降低了阻塞范围,并且在等待超时后,协调者或参与者会中断事务,避免了协调者单点问题,阶段三中协调者出现问题时,参与者会继续提交事务。
数据不一致问题依然存在,当在参与者收到preCommit
请求后等待doCommit
指令时,此时如果协调者请求中断事务,而协调者因为网络问题无法与参与者正常通信,会导致参与者继续提交事务,赵城事务不一致
2PC和3PC都无法保证数据绝对的一致性,一般为了预防这种问题,可以添加一个报警,比如监控到事务异常的时候,通过脚本自动补偿差异的信息