这里写目录标题
- 分布式事务的解决方案(难、重要)
分布式事务的解决方案(难、重要)
原文链接:多阶段提交方案(2PC、3PC)
原文链接:面试必问:分布式事务六种解决方案
业界目前主流的分布式事务解决方案主要有:多阶段提交方案(2PC、3PC)、补偿事务(TCC)和消息事务(主要是RocketMQ,基本思想也是多阶段提交方案,其他消息队列中间件并没有实现分布式事务)。这些方案的原理在此处不展开,目前网络中相应资料比较多,小结一下它们的特点:
多阶段提交方案(2PC、3PC)
常见的有二阶段和三阶段提交事务,需要额外的资源管理器来协调事务,数据一致性强,但是实现方案比较复杂,对性能的牺牲比较大(主要是需要对资源锁定,等待所有事务提交才能解锁),不适用于高并发的场景,目前比较知名的有阿里开源的fescar。
XA协议是什么
XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下:
图片来源:https://www.cnblogs.com/cxxjohnson/p/9145548.html
二阶段提交(2PC)
两阶段提交协议是协调所有分布式原子事务参与者,并决定提交或取消(回滚)的分布式算法。
协议参与者(1个协调者 + 多个事务参与者)
在两阶段提交协议中,系统一般包含两类机器(或节点):一类为协调者(coordinator),通常一个系统中只有一个;另一类为事务参与者(participants,cohorts或workers),一般包含多个,在数据存储系统中可以理解为数据副本的个数。
协议中假设每个节点都会记录写前日志(write-ahead log)并持久性存储,即使节点发生故障日志也不会丢失。协议中同时假设节点不会发生永久性故障而且任意两个节点都可以互相通信。
执行流程(哪两个阶段)
1. 请求阶段(commit-request phase,或称表决阶段,voting phase)
在请求阶段,协调者将通知事务参与者准备提交或取消事务,然后进入表决过程。
在表决过程中,参与者将告知协调者自己的决策:同意(事务参与者本地作业执行成功)或取消(本地作业执行故障)。
2. 提交阶段(commit phase)
在该阶段,协调者将基于第一个阶段的投票结果进行决策:提交或取消。
当且仅当所有的参与者同意提交事务协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者取消事务。
参与者在接收到协调者发来的消息后将执行响应的操作。
缺点
-
同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。
当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。 -
单点故障。由于协调者的重要性,一旦协调者发生故障。
参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题) -
数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
三阶段提交(3PC)
三阶段提交协议在协调者和参与者中都引入超时机制,并且把两阶段提交协议的第一个阶段拆分成了两步:询问,然后再锁资源,最后真正提交。
执行流程
1. 询问阶段(CanCommit)
协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。
2. 预提交阶段(PreCommit)
**协调者(Coordinator)根据事务参与者(Cohort)**的第一步反应情况来决定是否可以继续事务的PreCommit操作。
根据响应情况,有以下两种可能。
- 假如获得的反馈都是Yes响应,那么就会进行事务的预执行:
- 发送预提交请求。Coordinator向Cohort发送PreCommit请求,并进入Prepared阶段。
- 事务预提交。Cohort接收到PreCommit请求后,会执行事务操作,并将undo和redo信息(mysql的undolog和redolog?)记录到事务日志中。
- 响应反馈。如果Cohort成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。
- 假如有任何一个No响应,或者等待超时之后,就中断事务:
- 协调者发送中断请求。Coordinator向所有Cohort发送abort请求。
- 事务参与者中断事务。Cohort收到来自Coordinator的abort请求之后(或超时之后,仍未收到Cohort的请求),执行事务的中断。
3. 确认提交阶段(doCommit)
该阶段进行真正的事务提交,也可以分为以下两种情况:
- 执行提交
- 发送提交请求。Coordinator接收到Cohort发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有Cohort发送doCommit请求。
- 事务提交。Cohort接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
- 响应反馈。事务提交完之后,向Coordinator发送ACK响应。
- 完成事务。Coordinator接收到所有Cohort的ACK响应之后,完成事务。
- 中断事务
Coordinator没有接收到Cohort发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。
补偿事务(TCC:Try + Confirm + Cancel)
一般也叫TCC,因为每个事务操作都需要提供三个操作尝试(Try)、确认(Confirm)和补偿/撤销(Cancel),数据一致性的强度比多阶段提交方案低,但是实现的复杂度会有所降低(低??),比较明显的缺陷是每个业务事务需要实现三组操作,有可能出现过多的补偿方案的代码;另外有很多场景TCC是不合适的。
tips个人理解:Try生成一个草稿,comfirm把草稿变成真的,cancel删除草稿
操作步骤
-
Try 阶段主要是对业务系统做检测及资源预留
-
Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
-
Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
本地消息表(实现的是最终一致性)
本地消息表其实就是利用了各系统本地的事务来实现分布式事务。
本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。
然后再去调用下一个操作,如果下一个操作调用成功了好说,消息表的消息状态可以直接改成已成功。
如果调用失败也没事,会有 后台任务定时去读取本地消息表,筛选出还未成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。
这时候有可能消息对应的操作不成功,因此也需要重试,重试就得保证对应服务的方法是幂等的,而且一般重试会有最大次数,超过最大次数可以记录下报警让人工处理。
消息事务(RocketMQ实现、半消息)
第一步先给 Broker 发送事务消息即半消息,半消息不是说一半消息,而是这个消息对消费者来说不可见,然后发送成功后发送方再执行本地事务。
再根据本地事务的结果向 Broker 发送 Commit 或者 RollBack 命令。
并且 RocketMQ 的发送方会提供一个反查事务状态接口(check接口),如果一段时间内半消息没有收到任何操作请求,那么 Broker 会通过反查接口得知发送方事务是否执行成功,然后执行 Commit 或者 RollBack 命令。
图片来源:https://baijiahao.baidu.com/s?id=1662095398693413299&wfr=spider&for=pc
如果是Commit 那么订阅方就能收到这条消息,然后再做对应的操作,做完了之后再消费这条消息即可。
如果是 RollBack 那么订阅方收不到这条消息,等于事务就没执行过。
可以看到通过 RocketMQ 还是比较容易实现的,RocketMQ 提供了事务消息的功能,我们只需要定义好事务反查接口即可。
最大努力通知(是一种思想:尽可能的完成最终一致性)
本地消息表也可以算最大努力,消息事务也可以算最大努力。
就本地消息表来说会有后台任务定时去查看未完成的消息,然后去调用对应的服务,当一个消息多次调用都失败的时候可以记录下然后引入人工,或者直接舍弃。这其实算是最大努力了。
事务消息也是一样,当半消息被commit了之后确实就是普通消息了,如果订阅者一直不消费或者消费不了则会一直重试,到最后进入死信队列。其实这也算最大努力。
所以最大努力通知其实只是表明了一种柔性事务的思想:我已经尽力我最大的努力想达成事务的最终一致了。
适用于对时间不敏感的业务,例如短信通知。