在聊起分布式事务前先回顾下事务的概念。
数据库事务
基本概念
事务就是一个程序执行单元,里面的操作要么全部成功,要么全部执行失败。不允许只成功一半另一半失败的事情发生。比如在一个事务内,进行了两次数据库更新操作,那么只要其中一个失败了就两次更新操作全部失败。
基本特性
Atomicity(原子性):是说事务是一个不可分割的整体,所有操作要么全做,要么全不做;只要事务中有一个操作出错,回滚到事务开始前的状态的话,那么之前已经执行的所有操作都是无效的,都应该回滚到开始前的状态。
Consistency(一致性):是说事务执行前后,数据从一个状态到另一个状态必须是一致的,比如A向B转账( A、B的总金额就是一个一致性状态),不可能出现A扣了钱,B却没收到的情况发生。
Isolation(隔离性): 多个并发事务之间相互隔离,不能互相干扰。关于事务的隔离性,可能不是特别好理解,这里的并发事务是指两个事务操作了同一份数据的情况;而对于并发事务操作同一份数据的隔离性问题,则是要求不能出现脏读、幻读的情况,即事务A不能读取事务B还没有提交的数据,或者在事务A读取数据进行更新操作时,不允许事务B率先更新掉这条数据。而为了解决这个问题,常用的手段就是加锁了,对于数据库来说就是通过数据库的相关锁机制来保证。
Durablity(持久性):事务完成后,对数据库的更改是永久保存的,不能回滚。
分布式事务
两阶段提交(2PC)
两阶段提交将分布式事务分为两个阶段,两个阶段分别为提交请求(投票)和提交(执行)。协调者根据参与者的响应来决定是否要真正地执行事务。
提交请求(投票)阶段
分布式事务的发起方在像分布式事务协调者(Coordinator)发送请求时,Coordinator首先会分别向参与者(Partcipant)节点分别发送事务预处理请求,称之为prepare(或者Vote Request)。
1、协调者向所有参与者发送prepare请求与事务内容,询问是否可以提交事务,并等待参与者响应。
2、参与者执行事务中包含的操作,并记录undo log(用于回滚)和redo log(用于重放),但不真正提交。
3、参与者向协调者返回事务操作的执行结果,成功返回yes,失败返回no。
提交(执行)
分为成功和失败两种情况
1、所有的事物参与者都返回YES,说明事物可以提交
2、协调者向所有参与者发送commit请求。
3、参与者收到commit请求后,将事务真正地提交上去,并释放占用的事务资源,并向协调者返回ack。
4、协调者收到所有参与者的ack消息,事务成功完成。
若有参与者返回no(反馈vote_abort消息)或者超时未返回,说明事务中断,需要回滚:
1、协调者向所有参与者发送rollback请求(global_rollback)。
2、参与者收到rollback请求后,根据undo日志回滚到事务执行前的状态,释放占用的事务资源,并向协调者返回ack。
3、协调者收到所有参与者的ack消息,事务回滚完成。
两阶段提交通过增加了事务协调者(Coordinator)的角色来通过两个阶段的处理流程来解决分布式系统中一个事务需要跨越多个服务节点的数据一致性问题。
分析XA-两阶段提交协议问题点
性能问题。在执行流程中,当协调者回复vote_commit/vote_rollback请求时都需要等待协调者下一步指令,此时各个节点都处于阻塞状态。如果是数据库操作,那么数据库资源将会存在被占用情况,只有当所有节点准备完毕,协调者才通知全局提交或者回滚。性能影响较大。
协调者单点故障问题。事务协调者是XA模型的核心,事务协调者出现不可用情况,那么参与者可能会被阻塞在中间态。
丢失消息导致数据不一致问题。在第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就会导致节点间数据的不一致问题。
三阶段提交(3PC)
两阶段提交当第二阶段Coordinator在收到Participant的vote_request请求后故障导致(网络异常或者宕机),此时Participant就会一直处于本地事务的挂起状态,长期占用系统资源,严重导致系统崩溃。
三阶段提交(3PC),在两阶段提交的基础上增加了CanCommit阶段,且引入了超时机制。一旦事务参与者迟迟没有收到协调者的Commit请求,就会自动进行本地commit,这样相对有效地解决了协调者单点故障的问题。
第一阶段:CanCommit阶段
第一阶段事务问询操作。
1、事务发起者发送请求,协调者(Coordinator)向参与者发送can-commit请求,询问"你们是否可以完成本次事务"。
2、参与者对自身逻辑进行事务尝试,检查自身状态是否健康(或者获取数据库锁),确认是否有能力进行事务操作。
3、参与者回复协调者YES/NO,告知认为自身可以完成事务。
第二阶段:PreCommit阶段
当阶段一中所有参与者都返回YES,那么就会进入到PreCommit阶段进行事务提交。
1、协调者(Coordinator)向参与者发送pre-commit请求
2、参与者收到协调者发送的pre-commit后,执行事务操作(执行业务),同时写入undo log 以及redo log ,此时处于未提交(uncommit)阶段。
3、参与者向协调者反馈“ACK”,表示我已经准备完毕等待提交事务或者下一步命令。
如果阶段一中有任何一个参与者节点返回的结果是No响应,或者协调者在等待参与者节点反馈的过程中超时(2PC中只有协调者可以超时,参与者没有超时机制)。整个分布式事务就会中断,协调者就会向所有的参与者发送“abort”请求。
第三阶段:DoCommit阶段
在阶段二中如果所有的参与者节点都可以进行PreCommit提交,那么协调者就会从“预提交状态”-》“提交状态”。
1、协调者在收到全部参与者的ready-commit请求,向全部参与者发送global-commit(do-commit)请求
2、参与者收到global-commit指令后执行事务提交操作,并向协调者节点反馈“Ack”消息
3、协调者收到所有参与者的Ack消息后完成事务,返回处理成功结果。
相反,如果有一个参与者节点未完成PreCommit的反馈或者反馈超时,那么协调者都会向所有的参与者节点发送abort请求,从而中断事务。
相比较2PC而言,3PC对于协调者(Coordinator)和参与者(Partcipant)都设置了超时时间,而2PC只有协调者才拥有超时机制。这解决了一个什么问题呢?这个优化点,主要是避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。
另外,通过CanCommit、PreCommit、DoCommit三个阶段的设计,相较于2PC而言,多设置了一个缓冲阶段保证了在最后提交阶段之前各参与节点的状态是一致的。
以上就是3PC相对于2PC的一个提高(相对缓解了2PC中的前两个问题),但是3PC依然没有完全解决数据不一致的问题。
补偿事务(TCC)
事务消息
RabbitMQ事务实现-事务消息,通过实现RabbitMQ接口方法,自实现事务反查方法实现事务。
详见MQ事务实现 -
参考文章分布式事务之深入理解什么是2PC、3PC及TCC协议? - 无敌的码农 - 博客园