今天,我给大家分享一下我对这个问题的理解。
1.分布式事务应用场景
随着应用的拆分,从单体架构变成分布式架构,那么每个服务或者模块也会有自己的数据库。一个业务流程的完成需要经过多次的接口调用或者多条MQ消息的发送。
浪漫和悲观并不冲突,我时常消极但又依旧觉得生活很美好,哪怕只是一束光照进了房间,也要认真对待它的到来。
那么问题来了,如果是执行多条SQL语句,数据库的本地事务可以保证原子性。
浪漫和悲观并不冲突,我时常消极但又依旧觉得生活很美好,哪怕只是一束光照进了房间,也要认真对待它的到来。
但,如果是一条SQL操作,再加一条MQ的操作,如何才能把它们两个放在同一个逻辑单元里面执行呢?是先执行SQL还是先发送MQ呢?
浪漫和悲观并不冲突,我时常消极但又依旧觉得生活很美好,哪怕只是一束光照进了房间,也要认真对待它的到来。
我们来分析一下情况,如果是先发送MQ消息,再执行SQL。这个时候就要分为两种情况:
第1种情况:如果发送MQ失败了,当然SQL也就不会执行了。
第2种情况:如果发送MQ成功了,而本地数据库SQL执行失败。比如出现了网络异常,主键重复或者字段超长等等。
浪漫和悲观并不冲突,我时常消极但又依旧觉得生活很美好,哪怕只是一束光照进了房间,也要认真对待它的到来。
也就是说,下游的业务系统拿到了最新的数据,而自己本地的数据库反而没有。这个时候,本地数据库的数据跟其他系统已经登记的数据就不一样了,而发出去的消息又不可能撤回,有可能已经被消费了,这个叫做覆水难收。
浪漫和悲观并不冲突,我时常消极但又依旧觉得生活很美好,哪怕只是一束光照进了房间,也要认真对待它的到来。
因此,在分布式应用场景中,我们需要调整一下代码执行流程,也就是说必须先操作本地数据库,再发送MQ消息。如果本地数据库SQL执行成功,就算MQ消息发送失败,MQ还可以重发。
2.分布式事务实现原理
那基于上面的应用场景,应该如何设计发送消息的流程,才能让这两个操作要么都成功,要么都失败呢?
其实,可以参照XA两阶段提交的思想,把发送消息分成两步,然后把操作本地数据库也包括在这个流程中。那么,在介绍原理之前,先科普一下两个新的概念:
1、半消息(Half Message):也就是暂不能投递消费者的消息。发送方已经将消息成功发送到了 MQ 服务端,但是服务端未收到生产者对这条消息的二次确认,这个时候,这条消息会被标记为“暂不能投递”状态。
2、消息回查(Message Status Check):由于网络闪断、生产者应用重启等原因,导致某条事务消息的二次确认丢失,MQ 服务端通过扫描发现某条消息长期处于“半消息”时,需要主动向消息生产者询问该消息的最终状态,要么是Commit,要么Rollback。
下面给大家介绍一下RocketMQ的分布式事务实现原理,如图所示,一共分为七个步骤:
浪漫和悲观并不冲突,我时常消极但又依旧觉得生活很美好,哪怕只是一束光照进了房间,也要认真对待它的到来。
第一步:生产者向 MQ 服务端发送消息。
第二步:MQ 服务端将消息持久化成功之后,向发送方 ACK 确认消息已经发送成功,此时消息为半消息。
第三步:发送方开始执行本地数据库事务逻辑。
第四步:发送方根据本地数据库事务执行结果向 MQ Server 提交二次确认,MQ Server 收到 Commit 状态则将半消息标记为可投递,订阅方最终将收到该消息;MQ Server 收到 Rollback 状态则删除半消息,订阅方将不会接受该消息。
第五步:在断网或者是应用重启的特殊情况下,按步骤4提交的二次确认最终未到达 MQ Server,经过固定时间后 MQ Server 将对该消息发起消息回查。
第六步:发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
第七步:发送方根据检查得到的本地事务的最终状态再次提交二次确认,MQ Server 仍按照步骤4对半消息进行操作(Commit/Rollback)。
好了,以上就是我对RocketMQ分布式事务的理解。