回顧
??上一篇文章我們說到,各種分布式事務(wù)解決方案的特點(diǎn),其中最后提到了可靠消息事務(wù)最終一致性這種解決方案,而我們這篇文章的標(biāo)題也是它,沒錯,我們接下來要詳細(xì)的分析該解決方案的實(shí)現(xiàn)細(xì)節(jié)了,上一篇文章在介紹該解決方案時,已經(jīng)說了那個執(zhí)行流程分析圖,僅僅只是一個粗略圖而已,實(shí)際上,可靠消息事務(wù)最終一致性的設(shè)計(jì)是非常復(fù)雜的。那么為什么要花那么多時間來詳細(xì)分析它的實(shí)現(xiàn)細(xì)節(jié)呢?原因是接下來我們開發(fā)的這個分布式事務(wù)框架,就是使用可靠消息事務(wù)最終一致性的方案,選擇它的理由很簡單,就是因?yàn)樗容^復(fù)雜,所以實(shí)現(xiàn)的過程中,能學(xué)到的技術(shù)和得到的鍛煉比較多。
總體設(shè)計(jì)
先來看看以下這張?jiān)O(shè)計(jì)圖:

??這張?jiān)O(shè)計(jì)圖是在上一章的執(zhí)行流程基礎(chǔ)上,再補(bǔ)充了一些細(xì)節(jié),實(shí)際上,要完成可靠消息最終一致性,并不是僅僅依靠消息隊(duì)列就行了,還需要很多其他組件共同協(xié)作,這些組件在以不修改業(yè)務(wù)方法的前提下,通過組件或者擴(kuò)展的方式整合到項(xiàng)目中,具有可插拔性,這樣的話,才能做到對業(yè)務(wù)項(xiàng)目的侵入性低的目的。
??那么通過上述的總體設(shè)計(jì)流程圖,我們可以提取出以下幾個組件:1、攔截器組件(Interceptor),2、事務(wù)協(xié)調(diào)組件(Coordinator),3、事務(wù)日志存儲組件(Repository),4、可靠消息組件(MQ),5、補(bǔ)償調(diào)用組件(Invoker),6、定時器組件(Scheduler),7、初始化組件(Initiator)。接下來,我們詳細(xì)的看看,每一個組件是什么,做什么,并且它們之間如何協(xié)調(diào)工作的。
注意:以下所涉及的代碼不是真正的實(shí)現(xiàn),而是偽代碼
1、攔截器組件(Interceptor)
??通過設(shè)計(jì)圖的分析,我們可以知道,有兩個地方需要攔截的,一個是在發(fā)起RPC請求時,需要對請求進(jìn)行攔截,一個是RPC請求到達(dá)了遠(yuǎn)程目的方法后,執(zhí)行方法前的攔截。
- 發(fā)起RPC請求的攔截
??在發(fā)起RPC請求的攔截器中,我們需要告訴事務(wù)協(xié)調(diào)者,自己的角色是事務(wù)發(fā)起者的角色,這一步是至關(guān)重要的,因?yàn)橐粋€事務(wù)發(fā)起者需要保存這多個事務(wù)參與者的信息,舉個例子:轉(zhuǎn)賬(transfer)業(yè)務(wù)方法開啟了分布式事務(wù),并且transfer方法中有兩個遠(yuǎn)程方法,分別是扣錢(decrease)和加錢(augment)。那transfer就是事務(wù)發(fā)起者,decrease和augment就是事務(wù)參與者。
??transfer在分別調(diào)用decrease和augment者兩個遠(yuǎn)程方法時,先進(jìn)入了攔截器,把decrease和augment這兩個事務(wù)參與者的信息添加到transfer這個事務(wù)發(fā)起者中,也就是我們常說的一對多關(guān)系。
??在執(zhí)行完了transfer方法后,整個transfer這個事務(wù)發(fā)起者下有多少個事務(wù)參與者都添加完畢了,接下來就把自己的這個事務(wù)發(fā)起者角色告訴事務(wù)協(xié)調(diào)者,到這里,發(fā)起RPC請求的攔截器做的事情就結(jié)束,接下來就是事務(wù)協(xié)調(diào)者做的事情了,我們后面在來講事務(wù)協(xié)調(diào)者。 結(jié)合以下分析圖理解:
發(fā)起請求攔截分析圖 - 執(zhí)行業(yè)務(wù)方法前的攔截
??跟發(fā)起RPC請求的攔截類似,在RPC請求到達(dá)了目的方法后,開始執(zhí)行方法,但在執(zhí)行方法前,我們還需要做一些事情,就是把自己作為事務(wù)參與者的角色告知事務(wù)協(xié)調(diào)者,還是以攔截器的形式做這些事情,但是攔截器又如何知道自己將要執(zhí)行的業(yè)務(wù)方法就是事務(wù)參與者呢?這就要回到RPC請求上了:transfer遠(yuǎn)程RPC調(diào)用decrease和augment時,需要給這兩個地址追加一個參數(shù),該參數(shù)具體是什么東西沒有關(guān)系,只要能知道事務(wù)發(fā)起者是誰就行了(暫且我們叫該參數(shù)為transactionId),那么decrease和augment所在的過濾器獲取RPC參數(shù)名為transactionId的值,發(fā)現(xiàn)有值,那就把自己確定問事務(wù)參與者,并且通過transactionId的值知道,自己所屬的事務(wù)發(fā)起者是誰。 結(jié)合以下分析圖理解:
執(zhí)行方法攔截分析圖
2、事務(wù)協(xié)調(diào)組件(Coordinator)
??事務(wù)協(xié)調(diào)者是所有分布式事務(wù)解決方案都會有的一個核心處理器,它作為各個分布在不同JVM中的本地事務(wù)間接通訊的橋梁。在可靠消息事務(wù)最終一致性的解決方案中,它主要負(fù)責(zé)事務(wù)的發(fā)起、事務(wù)的參與、事務(wù)的提交、事務(wù)出錯處理、發(fā)送事務(wù)MQ消息等,凡是涉及到事務(wù)的生命周期操作,都經(jīng)過它做統(tǒng)籌處理。它直接操作下一個要講解的組件—事務(wù)日志存儲組件(Repository)。
3、事務(wù)日志存儲組件(Repository)
??事務(wù)日志存儲組件主要負(fù)責(zé)儲存事務(wù)日志的操作,事務(wù)日志的核心信息為:事務(wù)id、事務(wù)類和方法,事務(wù)狀態(tài)、事務(wù)角色、事務(wù)參與者集合。所以針對事務(wù)的操作非常多,會涉及到多線程和大并發(fā)的問題,所以這里到時候需要考慮線程安全和支持大并發(fā)的設(shè)計(jì)。 結(jié)合以下分析圖理解:

4、可靠消息組件(MQ)
??可靠消息組件顧名思義,就是發(fā)送MQ消息的,而消息體就是:事務(wù)發(fā)起者中的各個事務(wù)參與者詳細(xì)信息,還是以我們上面的例子說明:transfer在分別調(diào)用decrease和augment者兩個遠(yuǎn)程方法,transfer是事務(wù)發(fā)起者,decrease和augment是transfer的事務(wù)參與者,在transfer方法執(zhí)行完后,得到一個事務(wù)信息,該信息傳給了事務(wù)協(xié)調(diào)者,事務(wù)協(xié)調(diào)者會遍歷該事務(wù)中的參與者列表,每遍歷到一個事務(wù)參與者,就往該參與者所監(jiān)聽的MQ消息地點(diǎn)發(fā)送消息,消息體的核心信息為:“參與者的業(yè)務(wù)方法”。這樣的話參與者如果在執(zhí)行業(yè)務(wù)的過程中報錯了,還可以到MQ中獲取消息,重新執(zhí)行業(yè)務(wù)方法。結(jié)合以下分析圖理解(為了分析圖更直觀,刪減了一些流程):

5、補(bǔ)償調(diào)用組件(Invoker)
??消息補(bǔ)償組件起到的作用就是:各個事務(wù)參與者在各種異常情況下,沒法正常執(zhí)行業(yè)務(wù)方法,比如:事務(wù)發(fā)起者transfer調(diào)用了decrease和augment,但是augment所在的服務(wù)器宕機(jī),導(dǎo)致不能接受RPC請求,這樣事務(wù)就有問題了,decrease扣錢成功,augment加錢失敗。這時MQ就起作用了,我們剛剛已經(jīng)知道,在transfer方法執(zhí)行完后,會給MQ的decrease和augment地點(diǎn)發(fā)送消息,消息體有個核心信息:“執(zhí)行的業(yè)務(wù)方法”。那這樣,augment所在的服務(wù)器重啟后,馬上監(jiān)聽到MQ的augment地點(diǎn)消息,然后把消息交給事務(wù)補(bǔ)償調(diào)用組件(Invoker),Invoker重新調(diào)用消息體中的業(yè)務(wù)方法,完成事務(wù)補(bǔ)償。 同理,如果augment所在的服務(wù)器沒有宕機(jī),但卻在執(zhí)行業(yè)務(wù)方法的過程中報錯,則MQ會新增一條消息,等著augment獲取,并重新執(zhí)行業(yè)務(wù)方法。那為什么報錯了會自動往MQ發(fā)消息呢?這個涉及到定時器組件(Scheduler),我們下面再詳細(xì)分析定時器的作用。
??但這里需要特別注意的是,Invoker需要做好冪等性的操作,因?yàn)閠ransfer方法執(zhí)行完了,事務(wù)協(xié)調(diào)者就會往decrease和augment兩個事務(wù)參與者監(jiān)聽的消息地點(diǎn)發(fā)送消息,所以,不管decrease和augment是否正常執(zhí)行,消息都會發(fā)送出去,那問題就來了,通過RPC已經(jīng)正常調(diào)用了一次,然后監(jiān)聽到MQ消息又調(diào)用了一次,就調(diào)用了兩次了,所以事務(wù)補(bǔ)償調(diào)用組件需要做好冪等性,防止業(yè)務(wù)方法執(zhí)行多次。
6、定時器組件(Scheduler)
??定時器組件(Scheduler)就是我們剛剛提到的一種情況的問題:服務(wù)器正常運(yùn)行,能接收RPC遠(yuǎn)程調(diào)用請求,但是執(zhí)行過程中報錯,那這時,定時器就起作用了,定時器在項(xiàng)目初始化時就需要設(shè)置好。
??業(yè)務(wù)方法執(zhí)行報錯,進(jìn)入捕捉異常流程,通知事務(wù)協(xié)調(diào)者,把對應(yīng)的事務(wù)日志狀態(tài)修改為“錯誤”狀態(tài),然后定時器每隔一段時間掃描狀態(tài)為“錯誤”的事務(wù)日志,掃描到之后,即往MQ發(fā)送消息,那接下來的流程,又回到了事務(wù)補(bǔ)償?shù)牧鞒塘?。事?wù)補(bǔ)償成功后,需要把對應(yīng)的事務(wù)日志狀態(tài)修改為“提交”狀態(tài)。結(jié)合以下分析圖理解(為了分析圖更直觀,刪減了一些流程):

7、初始化組件(Initiator)
??這一個組件就不用多說了,我們設(shè)置定時器,監(jiān)聽MQ的Destination,還有做一些初始化參數(shù)設(shè)置等,都是需要在這里執(zhí)行。
總結(jié)
??分布式事務(wù)本身就是一件非常復(fù)雜的事情,所以在設(shè)計(jì)階段就要考慮的比較完善,這樣在具體實(shí)現(xiàn)的時候才不會有太多問題。而通過這篇文章的講解,已經(jīng)從宏觀上了解了這種“可靠消息事務(wù)最終一致性”解決方案的來龍去脈,相當(dāng)于設(shè)計(jì)稿就有了。那接下來,我們就要根據(jù)這個設(shè)計(jì)稿來做具體功能的實(shí)現(xiàn)。在寫代碼實(shí)現(xiàn)的過程中,由于會接觸到更細(xì)節(jié)的問題,這些細(xì)節(jié)又不可能在設(shè)計(jì)階段面面俱到,所以很可能會出現(xiàn)具體實(shí)現(xiàn)與設(shè)計(jì)不一樣的情況,這是正常的,只要在合理的調(diào)整范圍內(nèi),大體流程上并沒有改變就行了。

