两阶段提交(2pc)
需要一个全局事务管理器,作为协调者节点
第一阶段:
准备工作,询问各节点是否可以提交事务,节点响应是否可以提交,让事务进入准备状态,开始事务,写undo log。
第二阶段:
若都响应可以提交,则向所有节点发送提交事务。所有节点都确认后,结束事务。如果有一个准备阶段响应失败,则都不执行提交操作,而是执行事务回滚操作,发送发abort请求。
原理:通过预先占用资源(开始本地事务)的方式实现分布式事务。数据库的xa事务。
优点:保证较强的一致性。
缺点:性能差,牺牲了可用性。
存在问题
- 第二阶段的提交或abort请求因为网络问题丢失,会一直阻塞等待。
- 单点问题,协调者节点挂了,进行一半的事务会阻塞等待提交。重新选出协调者不能解决已阻塞等待的事务。
- 数据不一致,一部分执行commit,一部分网络丢失没执行。
机器异常挂了
- 协调者正常,参与方crash。若参与方在准备阶段crash,则协调者收不到Prepared回复,协调方不会发送commit命令,事务不会真正提交。若参与方在提交阶段提交,当它恢复后可以通过从其它参与方或者协调方获取事务是否应该提交,并作出相应的响应。
- 协调者crash,参与者正常。可以通过选出新的协调者解决。
- 协调者和参与方都crash,是两阶段提交无法完美解决的情况。尤其是当协调者发送出commit命令后,唯一收到commit命令的参与者也crash,此时其它参与方不能从协调者和已经crash的参与者那儿了解事务提交状态。但两阶段提交的前提条件之一是所有crash的节点最终都会恢复,所以当收到commit的参与方恢复后,其它节点可从它那里获取事务状态并作出相应操作。
2pc一般由数据库支持,mysql xa事务就是实现2pc:
https://blog.youkuaiyun.com/l1028386804/article/details/79769043
三阶段提交(3pc)
处理流程
事务处理能力询问–>处理后待提交–>提交确认(引入一个询问阶段,可以先预知每个库是否网络通信正常,可以正常响应,减少二阶段提交的abort回滚的情况
)
对比二阶段新增的机制
- 引入超时机制,第二阶段如果参与者超时没有接收到commit或abort请求,会自动commit(为什么是commit不是abort,因为已经有过第一阶段的询问,commit的几率高一些)。
- 如果协调者没收到部分参与者的确认,会发送abort请求。
存在问题
如果abort请求丢失,参与者自动commit,会导致数据不一致
补偿机制(TCC)
有点类似2pc,但是是从业务层面实现。分为三个阶段
1 try阶段。对系统进行检测和预留资源。
2 confirm阶段。执行业务逻辑并提交事务。
3 cancel阶段。在业务执行错误时,把已经提交了事务的执行反操作,将数据更改回原样。
优点:实现流程相对简单一些。
缺点:一致性相对差一些,而且需要写补偿代码,比如写了加钱的代码,要再写减钱的代码。而且补偿可能还出现失败的情况。
本地消息表
基于消息队列实现。
增加一个消息表存储消息发送记录和状态(发送失败的状态和消息还未处理的状态),存储消息和业务逻辑放在一个事务中确保都能完成。
发送端:
业务逻辑执行后,将消息信息和状态存到消息表中,然后发送mq。
消费端:
接收到消息后也进行相应的业务逻辑处理,处理成功则发个消息通知发送端删除消息表中的消息
。业务逻辑上的失败可以发个消息处理失败给发送端回滚数据并删除消息表中的数据(非系统异常失败的情况
)。
消息表的作用:
- 消息表的插入和业务逻辑修改表可以在同一个事务中,发送消息如果失败,可以保证消息表存储了这次消息发送失败的记录。(其实可以让消息发送处于业务事务中发送,这样报错也能回滚事务,但是因为发消息失败导致业务处理失败就
本末倒置
了) - 消费端消费失败时,发送端的消息就没办法得到确认,消息表的记录就不能删除。
发送端可以通过定时扫描消息表,将规定时间内(消息表存放个发送时间
)没有处理成功的消息、发送失败的消息
重新发送给消费端,让消费端重新处理。
避免重复消费:定时发送可能会可能会导致重复消费的问题,因此需要保证消息不被重复处理,可以给每条消息生成一个唯一id,放到消息头或消息体中,处理过的消息要存到表中或者缓存中,重新消费则不处理了。
优点:相比tcc的处理,即使补偿失败了,也会通过定时器定时扫描表中的数据进行处理。
缺点:追求的是最终一致性,不是强一致性,且需要用将消息表和业务耦合。降低了可用性,需要保证mq的可用性。
其实没有消息表也可以在消费失败时,拒绝消息,让消息回到原队列的方式,之后也可以重新消费处理。
mq事务消息
本地消息表主要解决了两个问题,一个是消息发送失败的处理,一个是消费者消费失败的处理。如果mq能够解决这两个问题,就可以不用通过消息表实现。
rocketmq就支持事务消息,方式也是二阶段提交。
发送消息失败处理
事务消息分为三个阶段
1 执行Prepared消息,获取到消息地址。
2 执行本地事务
3 发送确认消息,修改消息状态。
为了防止发送确认消息失败,rocketmq会定时(默认1分钟)
扫描集群中的事务消息,如果发现了Prepared
消息,会向消息发送者确认,即发送者需要提供一个确认接口。
消费系统层面的失败
消费者消息消费后,会回复确认消息。如果没有确认消息,mq会重新发送消息。
业务逻辑层面的失败,rockermq没有提供相应的补偿机制支持,即不会去回滚发送端的事务。比如支付系统扣钱发现钱不够扣,这种失败重试并没有意义。
优点:完全依赖mq,不用定义消息表
缺点:也是最终一致性,且不能应对业务逻辑失败。