四種基于MQ的分布式事務(wù)解決方案
在微服務(wù)的時(shí)代,分布式事務(wù)是繞不開(kāi)的話題,盡管在大多數(shù)場(chǎng)景下,我們并不需要使用分布式事務(wù),但是 不需要使用 不代表 可以不會(huì)使用,萬(wàn)一哪天真需要用到了呢?分布式事務(wù)是一個(gè)比較大的話題,今天我們來(lái)看看基于MQ的分布式事務(wù)解決方案。
在實(shí)際開(kāi)發(fā)中,為了簡(jiǎn)化分布式事務(wù),我們和其他服務(wù)交互,經(jīng)常會(huì)采用MQ的方式,我們先來(lái)看下如果采用MQ的方式和其他服務(wù)進(jìn)行交互,應(yīng)該怎么做。
采用MQ的方式和其他服務(wù)進(jìn)行交互
基于RocketMQ 事務(wù)消息+最大努力通知
RocketMQ提供了事務(wù)的消息的功能,我們來(lái)看下事務(wù)消息的原理:
Producer發(fā)送一個(gè)半消息到Broker;
Broker收到半消息后,響應(yīng)Producer,Broker會(huì)將半消息存儲(chǔ)到一個(gè)特殊的Topic,此時(shí)Consumer是不能消費(fèi)此消息的;
Producer執(zhí)行本地事務(wù);
Producer根據(jù)本地事務(wù)的執(zhí)行情況,告知Broker Commit或者Rollback,如果Commit,Broker會(huì)將消息投遞到正常的Topic,此時(shí)Consumer可以正常消費(fèi)此消息。
如果因?yàn)槟撤N原因,Broker未收到Producer的Commit或者Rollback,Broker會(huì)發(fā)起回查,Producer收到回查請(qǐng)求后,根據(jù)本地事務(wù)狀態(tài),重新響應(yīng)Broker Commit或者Rollback。
對(duì)于Producer而言,本地事務(wù)和發(fā)送消息是一致的,要么都成功,要么都失敗,但是無(wú)法保證Consumer的一致性,有不少人稱之為“單端事務(wù)”,但是RocketMQ還為Consumer提供了重試的功能,只要Consumer不返回消息消費(fèi)成功,Consumer還有16次機(jī)會(huì)可以重新消費(fèi)此消息,執(zhí)行自身的業(yè)務(wù)操作,相當(dāng)于最大努力通知。
這個(gè)方案可以說(shuō)是最常用的分布式事務(wù)解決方案了,不管是實(shí)現(xiàn),還是原理都比較簡(jiǎn)單,但是仔細(xì)想想此方案還是有缺點(diǎn)的:
和RocketMQ強(qiáng)綁定,因?yàn)橹挥蠷ocketMQ才提供了完善的事務(wù)消息的功能;
降低了可用性,如果Producer發(fā)送半消息失敗,流程就終止了;
代碼侵入性強(qiáng),Producer需要提供執(zhí)行本地事務(wù)、回調(diào)兩個(gè)方法。
基于本地消息表
此方案在RocketMQ事務(wù)消息推出之前,是采用較多的一個(gè)分布式事務(wù)解決方案,需要在庫(kù)中新建一張本地消息表,此表有如下核心字段:
topic:消息需要發(fā)送到哪個(gè)topic;
state:消息狀態(tài),有三種狀態(tài):1.未發(fā)送 2.發(fā)送失敗 3.發(fā)送成功;
retry_num:重試次數(shù);
time:消息產(chǎn)生的時(shí)間;
last_retry_time:最近重試時(shí)間;
message:消息內(nèi)容。
作為Producer來(lái)說(shuō),原本是執(zhí)行完本地業(yè)務(wù)后,直接將消息投遞到MQ,現(xiàn)在需要將消息保存至本地消息表,然后由定時(shí)任務(wù)讀取本地消息表,將需要推送的消息投遞到MQ,具體做法如下:
本地業(yè)務(wù)+插入本地消息表 組成一個(gè)大的本地事務(wù),以此保證兩者的原子性、一致性,本地業(yè)務(wù)+插入本地消息表兩個(gè)操作要么同時(shí)成功,要么同時(shí)失?。?br>
新增定時(shí)任務(wù),不斷的掃描本地消息表,將未成功投遞的消息進(jìn)行投遞,投遞成功,修改本地消息表的狀態(tài)字段,投遞失敗,修改本地消息表中的狀態(tài),并且重試次數(shù)+1,等待下一次重新投遞。
這個(gè)方案實(shí)現(xiàn)也非常簡(jiǎn)單,缺點(diǎn)也顯而易見(jiàn):
嚴(yán)重依賴Job;
及時(shí)性比較差,如果Job每10分鐘運(yùn)行一次,那可能就有10分鐘的延遲。如果Job每5分鐘運(yùn)行一次,那可能就有5分鐘的延遲。
不斷的掃描本地消息表,對(duì)數(shù)據(jù)庫(kù)也是一種壓力;
需要定期清理本地消息表。
基于內(nèi)存隊(duì)列+本地消息表
本地消息表這個(gè)方案還是不錯(cuò)的,有沒(méi)有辦法改善它的缺點(diǎn)呢?當(dāng)然有,這個(gè)方案就是對(duì)傳統(tǒng)的本地消息表方案進(jìn)行了改造,據(jù)說(shuō)部分二三線互聯(lián)網(wǎng)公司就是采用的此種方案,具體看圖(如果圖片看不清,可以將圖片下載到本地 或者 在新標(biāo)簽頁(yè)中查看圖片):
雖然此方案需要自己編碼實(shí)現(xiàn),但是整體來(lái)說(shuō),編碼難度不大。
不管是基于RocketMQ事務(wù)消息的分布式事務(wù)解決方案,還是基于本地消息表的分布式事務(wù)解決方案,還是基于內(nèi)存隊(duì)列+本地消息表的分布式事務(wù)解決方案,都有消息表的概念,只是消息表的具體存在形式不同,一個(gè)是以數(shù)據(jù)表的形式存在(存在了數(shù)據(jù)庫(kù)),一個(gè)是以Topic的形式存在(存在了Broker)。
以上三種方案都有一個(gè)局限性:和其他服務(wù)進(jìn)行交互,必須比較采用MQ的方式。
不采用MQ的方式和其他服務(wù)進(jìn)行交互
基于RocketMQ的延遲消息檢查方案
上述三個(gè)方案,都有一個(gè)局限性:和其他服務(wù)進(jìn)行交互,必須采用MQ的方式,如果不滿足這個(gè)條件,如何采用MQ的方式來(lái)實(shí)現(xiàn)分布式事務(wù)呢:采用基于RocketMQ的延遲消息檢查方案。
Producer發(fā)送延遲消息;
其他服務(wù)需要提供檢查接口,重試或者回滾接口;
Producer收到延遲消息后,檢查自身的業(yè)務(wù)操作是否執(zhí)行成功,根據(jù)具體情況,判斷是否需要重試或回滾,然后調(diào)用其他服務(wù)提供的檢查接口,檢查其他服務(wù)的業(yè)務(wù)操作是否執(zhí)行成功,再根據(jù)具體的情況判斷是否調(diào)用其他服務(wù)提供的重試、回滾接口。
此方案實(shí)現(xiàn)也比較簡(jiǎn)單,且容易理解,但是也有缺點(diǎn):
和RocketMQ強(qiáng)綁定,因?yàn)橹挥蠷ocketMQ才提供了完善的延遲消息的功能;
降低了可用性,如果Producer發(fā)送延遲消息失敗,流程就終止了。
作者:碼出宇宙
歡迎關(guān)注微信公眾號(hào) :碼出宇宙