【转】分布式事务的常见解决方案
一、事務(wù)起步
1. 什么是事務(wù)
事務(wù)這種東西大家都耳熟能詳了,通常指由一組操作組成的一個工作單元,這一整個組合要么全部成功,要么全部失敗。
2. 本地事務(wù)
在計算機系統(tǒng)中,更多的是通過關(guān)系型數(shù)據(jù)庫來控制事務(wù),這是利用數(shù)據(jù)庫本身的事務(wù)特性來實現(xiàn)的,因此叫數(shù)據(jù)庫事務(wù),由于應(yīng)用主要靠關(guān)系數(shù)據(jù)庫來控制事務(wù),而數(shù)據(jù)庫和應(yīng)用在同一服務(wù)器,所以基于關(guān)系型數(shù)據(jù)庫的事務(wù)又被稱為本地事務(wù)。
數(shù)據(jù)庫事務(wù)具有原子性(Atomacity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability)。
3. 分布式事務(wù)
隨著互聯(lián)網(wǎng)的快速發(fā)展,軟件系統(tǒng)由原來的單體應(yīng)用轉(zhuǎn)變?yōu)榉植际綉?yīng)用。分布式系統(tǒng)會把一個應(yīng)用系統(tǒng)拆分為可獨立部署的多個服務(wù),因此需要服務(wù)與服務(wù)之間遠程協(xié)作才能完成事務(wù)操作,這種分布式系統(tǒng)環(huán)境下由不同的服務(wù)之間通過網(wǎng)絡(luò)遠程協(xié)作完成事務(wù)稱為分布式事務(wù)。
以上面圖示舉例,當(dāng)需要創(chuàng)建訂單的時候,將會涉及到訂單和庫存兩個操作。那么左圖操作兩個系統(tǒng)最終需要寫入兩個數(shù)據(jù)庫,我們需要保證分布式事務(wù)的一致性,這一點應(yīng)該很容易理解,如果我們只創(chuàng)建了訂單而沒有去減少相應(yīng)的庫存量,就會出現(xiàn)超賣的現(xiàn)象。同樣的像中間,雖然最終只涉及到一個數(shù)據(jù)庫,但是同樣需要保證一致性,右圖同理。
二、CAP理論與BASE理論
1. CAP理論
CAP是一致性(Consistency)、可用性(Availability)、分區(qū)容忍性(Partition Tolerance)三個詞語的縮寫,我們先簡單解釋下這三個詞語:
- 一致性:服務(wù)A、B、C三個節(jié)點都存儲了用戶數(shù)據(jù),要求三個節(jié)點在寫操作后的讀操作可以讀取到的數(shù)據(jù)都是最新的狀態(tài);
- 可用性:可用性即整個系統(tǒng)可以外提供服務(wù);
- 分區(qū)容忍性:分布式系統(tǒng)的節(jié)點往往都是分布在不同的機器上進行網(wǎng)絡(luò)隔離開的,這意味著必然會有網(wǎng)絡(luò)斷開的風(fēng)險,這個網(wǎng)絡(luò)斷開的場景我們稱為分區(qū)容忍性;
那么在網(wǎng)絡(luò)分區(qū)出現(xiàn),也就是有多個節(jié)點分布在不同的子網(wǎng)中時,如何實現(xiàn)一致性呢?過程大概如下:
所以,如果我們在數(shù)據(jù)庫同步期間鎖定從數(shù)據(jù)庫,那么可用性就無法保證;如果不鎖定,那么就會有訪問向從數(shù)據(jù)庫讀取數(shù)據(jù),可能會讀取到舊的數(shù)據(jù),此時又無法保證一致性。
所以用一句話概括CAP原理就是:當(dāng)網(wǎng)絡(luò)分區(qū)發(fā)生時,一致性和可用性難以兩全。只能選擇CP或者AP。
2. BASE理論
2.1 強一致性和最終一致性
CAP理論告訴我們一個分布式系統(tǒng)只能同時滿足CAP三項中的兩項,其中AP在實際應(yīng)用中較多,AP即舍棄一致性,保證可用性和分區(qū)容忍性,但是在實際生產(chǎn)中很多場景也是需要實現(xiàn)一致性的。
比如主數(shù)據(jù)庫向從數(shù)據(jù)庫中同步數(shù)據(jù),即使不要一致性,但是最終也要將數(shù)據(jù)同步成功來保證數(shù)據(jù)一致,這種一致性和CAP中的一致性不同,CAP中的一致性是要求任何時間查詢每個節(jié)點數(shù)據(jù)都必須一致,它所強調(diào)的是一致性,但是最終一致性是允許在一段時間內(nèi)各節(jié)點的數(shù)據(jù)不一致,但是經(jīng)過一段時間每個節(jié)點的數(shù)據(jù)必須一致,它所強調(diào)的是最終一致性。
2.2 Base理論介紹
BASE 是 Basically Available(基本可用)、Soft State(軟狀態(tài))和Eventually consistent(最終一致性)三個短語的縮寫。BASE理論是對CAP中AP的一個擴展,通過犧牲強一致性來獲得可用性,當(dāng)出現(xiàn)故障允許部分不可用但要保核心功能可用,允許數(shù)據(jù)在一段時間內(nèi)是不一致的,但是最終要達到一致狀態(tài)。滿足BASE理論的事務(wù),我們稱之為“柔性事務(wù)”。
- 基本可用:分布式系統(tǒng)在出現(xiàn)故障時,允許損失部分可用功能,保證核心功能可用。如,電商網(wǎng)站交易付款出現(xiàn)問題了,商品依然可以正常瀏覽;
- 軟狀態(tài):由于不要求強一致性,所以BASE允許系統(tǒng)中存在中間狀態(tài)(也叫軟狀態(tài)),這個狀態(tài)不影響系統(tǒng)可用性,如訂單的“支付中”、“數(shù)據(jù)同步中”等狀態(tài),待數(shù)據(jù)最終一致后狀態(tài)改為“成功”狀態(tài);
- 最終一致:最終一致是指經(jīng)過一段時間后,所有節(jié)點數(shù)據(jù)都會達成一致。如訂單的“支付中”狀態(tài),最終會變成“支付成功”或者“支付失敗“,使訂單狀態(tài)與實際交易結(jié)果達成一致,但需要一定時間的延遲、等待。
三、2PC兩階段提交
2PC即兩階段提交(Two-phase Commit, 2PC),通過引入?yún)f(xié)調(diào)者(Coordinate)來協(xié)調(diào)參與者的行為,并最終決定這些參與者是否要真正執(zhí)行事務(wù)。
1. 運行過程
1.1 準(zhǔn)備階段
協(xié)調(diào)者詢問參與者事務(wù)是否執(zhí)行成功,參與者發(fā)回事務(wù)執(zhí)行結(jié)果,如下圖所示:
1.2 提交階段
如果事務(wù)在每個參與者上都執(zhí)行成功,事務(wù)協(xié)調(diào)者發(fā)送通知讓參與者提交事務(wù);否則,協(xié)調(diào)者發(fā)送通知讓參與者回滾事務(wù)。
注意:在準(zhǔn)備階段,參與者只是執(zhí)行了事務(wù),但是并沒有提交,只有在提交階段接收到協(xié)調(diào)者發(fā)來的通知后,才進行提交或者回滾。
2. XA方案
2PC的傳統(tǒng)方案是在數(shù)據(jù)庫層面實現(xiàn)的,如Oracle和MySQL都支持2PC協(xié)議,為了統(tǒng)一標(biāo)準(zhǔn)和減少行業(yè)內(nèi)不必要的對接成本,需要制定標(biāo)準(zhǔn)化的處理模型及接口標(biāo)準(zhǔn),國際開放組織Open Group定義了分布式事務(wù)處理模型DTP(Distributed Transaction Processing Reference Model)。
下面以新用戶注冊時送積分的案例來說明:
執(zhí)行流程如下:
DTP模型定義如下角色:
- AP(Application Program):即應(yīng)用程序,可以理解為使用DTP分布式事務(wù)的程序;
- RM(Resource Program):即資源管理器,可以理解為事務(wù)的參與者,一般情況下是指一個數(shù)據(jù)庫實例,通過資源管理器對該數(shù)據(jù)庫進行控制,資源管理器控制著分支事務(wù);
- TM(Transaction Manager):事務(wù)管理器,負責(zé)協(xié)調(diào)和管理事務(wù),事務(wù)管理器控制著全局事務(wù),管理事務(wù)生命周期,并協(xié)調(diào)各個RM。全局事務(wù)是指分布式事務(wù)處理環(huán)境中,需要操作多個數(shù)據(jù)庫共同完成一個工作,這個工作即是一個全局事務(wù)。
DTP模型定義TM和RM之間通訊的接口規(guī)范叫XA,簡單理解為數(shù)據(jù)庫提供的2PC接口協(xié)議,基于數(shù)據(jù)庫的XA協(xié)議來實現(xiàn)2PC又稱為XA方案。
以上三個交互角色之間的交互方式如下:
XA方案的缺陷:
- 需要本地數(shù)據(jù)庫支持XA協(xié)議;
- 資源鎖需要等待兩個階段結(jié)束才釋放,性能較差。
3. Seata方案
Seata是由阿里開源的分布式事務(wù)框架。傳統(tǒng)2PC的問題在Seata中得到了解決,通過對本地關(guān)系數(shù)據(jù)庫的分支事務(wù)的協(xié)調(diào)來驅(qū)動完成全局事務(wù),是工作在應(yīng)用層的中間件。主要優(yōu)點是性能較好,且不長時間占用連接資源,以高效且對事務(wù)零侵入的方式解決微服務(wù)場景下面臨的分布式事務(wù)問題,它目前提供AT模式(即2PC)及TCC模式的分布式事務(wù)解決方案。
3.1 設(shè)計思想
Seata的設(shè)計目標(biāo)包括了對業(yè)務(wù)的無侵入,因此從業(yè)務(wù)無侵入的2PC方案著手,在傳統(tǒng)的2PC的基礎(chǔ)上演進,并解決2PC方案面臨的問題。
Seata把一個分布式事務(wù)理解成一個包含了若干個分支事務(wù)的全局事務(wù)。全局事務(wù)的職責(zé)是協(xié)調(diào)其下管轄的分支事務(wù)達成一致,要么一起提交,要么一起失敗回滾。此外,通常分支事務(wù)本身就是一個關(guān)系數(shù)據(jù)庫的本地事務(wù),下圖是全局事務(wù)與分支事務(wù)的關(guān)系圖:
與傳統(tǒng)的2PC模型相比,Seata定義了三個組件來協(xié)調(diào)分布式事務(wù)的管理過程:
- Transaction Coordinator(TC):事務(wù)協(xié)調(diào)器,它是獨立的中間件,需要獨立部署運行,它維護全局事務(wù)的運行狀態(tài),接收TM指令發(fā)起全局事務(wù)的提交與回滾,負責(zé)與RM通信協(xié)調(diào)各個分支事務(wù)的提交或回滾;
- Transaction Manager(TM):事務(wù)管理器,TM需要嵌入應(yīng)用程序中工作,它負責(zé)開啟一個全局事務(wù),并最終向TC發(fā)起全局提交或全局回滾的指令;
- Resource Manager(RM):控制分支事務(wù),負責(zé)分支注冊、狀態(tài)匯報,并接收事務(wù)協(xié)調(diào)器TC的指令,驅(qū)動分支(本地)事務(wù)的提交和回滾。
3.2 執(zhí)行流程
依舊以新用戶注冊送積分舉例Seata的分布式事務(wù)過程:
具體的執(zhí)行流程如下:
3.3 Seata的優(yōu)勢
架構(gòu)層次方面,傳統(tǒng)2PC方案的RM實際上是在數(shù)據(jù)庫層,RM本質(zhì)上就是數(shù)據(jù)庫本身,通過XA協(xié)議實現(xiàn),而Seata的RM是以jar包的形式作為中間件層部署在應(yīng)用程序這一側(cè)的。
兩階段提交方面,傳統(tǒng)2PC無論第二階段的決是commit還是rollback,事務(wù)性資源的鎖都要保持到Phase2完成才能釋放,而Seata的做法是在Phase1就將本地事務(wù)提交,這樣就可以省去 Phase2 持鎖的時間,整體提高效率。
3.4 二階段回滾
二階段如果是提交的話就很容易理解,因為”業(yè)務(wù)SQL“在一階段已經(jīng)提交至數(shù)據(jù)庫,所以Seata框架只需將一階段保存的快照數(shù)據(jù)和行鎖刪掉,完成數(shù)據(jù)清理即可。
二階段如果是回滾的話,Seata就需要回滾一階段已經(jīng)執(zhí)行的”業(yè)務(wù)SQL“,還原業(yè)務(wù)數(shù)據(jù)。回滾方式便是用”before image“還原業(yè)務(wù)數(shù)據(jù);但在還原前要首先校驗臟寫,對比”數(shù)據(jù)庫當(dāng)前業(yè)務(wù)數(shù)據(jù)“和”after image“,如果兩份數(shù)據(jù)完全一致就說明沒有臟寫,可以還原業(yè)務(wù)數(shù)據(jù),如果不一致就說明有臟寫,出現(xiàn)臟寫就需要轉(zhuǎn)人工處理。
四、TCC事務(wù)補償
TCC事務(wù)補償是基于2PC實現(xiàn)的業(yè)務(wù)層事務(wù)控制方案,TCC這一詞是由Try、Confirm和Cancel三個單詞的首字母,含義如下:
- Try階段做業(yè)務(wù)檢查(一致性)及資源預(yù)留(隔離),此階段僅為一個初步操作,它和后續(xù)的Confirm一起才能真正構(gòu)成一個完整的業(yè)務(wù)邏輯;
- Confirm階段是做確認(rèn)提交,Try階段所有分支事務(wù)執(zhí)行成功后開始執(zhí)行Confirm。通常情況下,采用TCC則認(rèn)為Confirm階段是不會出錯的。即:只要Try成功,Confirm一定成功。若Confirm階段真的出錯了,需引入重試機制或人工處理;
- Cancel階段是在業(yè)務(wù)執(zhí)行錯誤需要回滾的狀態(tài)下執(zhí)行分支事務(wù)的業(yè)務(wù)取消,預(yù)留資源釋放。通常情況下,采用TCC則認(rèn)為Cancel階段也是一定會成功的。若Cancel階段真的出錯了,需引入重試機制或人工處理。
TM首先發(fā)起所有的分支事務(wù)的Try操作,任何一個分支事務(wù)的Try操作執(zhí)行失敗,TM將會發(fā)起所有分支事務(wù)的Cancel操作,若try操作全部成功,TM將會發(fā)起所有的分支事務(wù)的Confirm操作,其中Conrirm/Cancel操作若執(zhí)行失敗,TM會進行那個重試。
我們用一個下單同時減少庫存的業(yè)務(wù)來進行說明:
Try
下單業(yè)務(wù)由訂單服務(wù)和庫存服務(wù)協(xié)同完成,在try階段訂單和庫存服務(wù)完成檢查和預(yù)留資源;
訂單服務(wù)檢查當(dāng)前是否滿足提交訂單的條件(比如:當(dāng)前存在未完成的訂單,不允許提交新訂單);
庫存服務(wù)檢查是否有充足的資源,并鎖定資源;
Confirm
訂單服務(wù)和庫存服務(wù)完成Try后開始執(zhí)行資源操作;
訂單服務(wù)向訂單寫一條訂單信息;
庫存服務(wù)減去庫存。
Cancel
如果訂單服務(wù)和庫存服務(wù)有一方出現(xiàn)失敗則全部操作取消;
訂單服務(wù)需要刪除新增的訂單信息。
庫存服務(wù)將減去的庫存再還原。
1. 空回滾
在沒有調(diào)用TCC資源Try方法的情況下,調(diào)用了二階段的Cancel方法,Cancel方法需要識別出這是一個空回滾,然后直接返回成功。
出現(xiàn)原因是當(dāng)一個分支事務(wù)所在服務(wù)宕機或網(wǎng)絡(luò)異常,分支事務(wù)調(diào)用記錄為失敗,這個時候其實是沒有執(zhí)行Try階段,當(dāng)故障恢復(fù)后,分布式事務(wù)進行回滾則會調(diào)用二階段的Cancel方法,從而形成空回滾。
解決思路的關(guān)鍵是要識別出這個空回滾。思路很簡單就是需要知道一階段是否執(zhí)行了,如果執(zhí)行了,那就是正?;貪L;如果沒執(zhí)行,那就是空回滾。前面已經(jīng)說過TM在發(fā)起全局事務(wù)時生成全局事務(wù)記錄,全局事務(wù)ID貫穿整個分布式事務(wù)調(diào)用鏈。再額外增加一張分支事務(wù)記錄表,表中有全局事務(wù)ID貫穿和分支事務(wù)ID,第一階段Try方法里會插入一條記錄,表示一階段執(zhí)行了。Cancel接口里讀取該記錄,如果該記錄存在,則正常回滾;如果該記錄不存在,則是空回滾。
2. 冪等性
TM(即事務(wù)管理器)在發(fā)起全局事務(wù)時生成全局事務(wù)記錄,全局事務(wù)ID貫穿整個分布式事務(wù)調(diào)用鏈條,用來記錄事務(wù)上下s文,追蹤和記錄狀態(tài),由于Confirm和Cancel失敗需進行重試,因此需要實現(xiàn)為冪等。冪等性是指同一操作無論請求多少次,其結(jié)果都相同。
所以為了保證TCC二階段提交重試機不會引發(fā)數(shù)據(jù)不一致,要求TCC的二階段Try、Confirm和Cancel接口保證冪等,這樣不會重復(fù)使用或者釋放資源。如果冪等控制沒做好,很有可能導(dǎo)致數(shù)據(jù)不一致等嚴(yán)重問題。
3. 懸掛
懸掛就是對于一個分布式事務(wù),其二階段Cancel接口比Try接口先執(zhí)行。
出現(xiàn)原因是在RPC調(diào)用分支事務(wù)Try時,先注冊分支事務(wù),再執(zhí)行RPC調(diào)用,如果此時RPC調(diào)用的網(wǎng)絡(luò)發(fā)生擁堵,通常RPC調(diào)用是有超時時間的,RPC超時以后,TM就會通知RM回滾分布式事務(wù),可能回滾完成后,RPC請求才到達參與者真正執(zhí)行,而一個Try方法預(yù)留的業(yè)務(wù)資源,只有該分布式事務(wù)才能使用,該分布式事務(wù)第一階段預(yù)留的業(yè)務(wù)資源就再也沒有人能夠處理了,對于這種情況,我們稱之為懸掛,即業(yè)務(wù)資源預(yù)留后沒法繼續(xù)處理。
解決思路是如果二階段執(zhí)行完成,那一階段就不再繼續(xù)執(zhí)行。在執(zhí)行一階段事務(wù)時判斷在該全局事務(wù)下,”分支事務(wù)記錄“表中是否已有二階段事務(wù)記錄,如果有則不執(zhí)行Try。
五、可靠消息最終一致性
可靠消息最終一致性方案是指當(dāng)事務(wù)發(fā)起方執(zhí)行完成本地事務(wù)后并發(fā)出一條消息,事務(wù)參與方(消息消費者)一定能夠接收消息并處理事務(wù)成功,此方案強調(diào)的是只要消息發(fā)給事務(wù)參與方最終事務(wù)要達到一致。
此方案是利用消息中間件完成,如下圖:
事務(wù)發(fā)起方(消息生成方)將消息發(fā)給消息中間件,事務(wù)參與方從消息中間件接收消息,事務(wù)發(fā)起方和消息中間件之間,事務(wù)參與方(消息消費方)和消息中間件之間都是通過網(wǎng)絡(luò)通信,由于網(wǎng)絡(luò)通信的不確定性會導(dǎo)致分布式事務(wù)問題。
因此可靠消息最終一致性解決方案要解決以下幾個問題:
1. 方案問題
1.1 本地事務(wù)與消息發(fā)送的原子性問題
本地事務(wù)與消息發(fā)送的原子性問題即:事務(wù)發(fā)起方在本地事務(wù)執(zhí)行成功后消息必須發(fā)送出去,否則就丟棄消息。即實現(xiàn)本地消息和消息發(fā)送的原子性,要么都成功,要么都失敗。本地事務(wù)與消息發(fā)送的原子性問題是實現(xiàn)可靠消息最終一致性方案的關(guān)鍵問題。
先來嘗試下這種操作,先發(fā)送消息,再操作數(shù)據(jù)庫:
begin transaction:// 1. 發(fā)送MQ// 2. 數(shù)據(jù)庫操作 commit transaction;- 1
- 2
- 3
- 4
這種情況下無法保證數(shù)據(jù)庫操作與發(fā)送消息的一致性,因為可能發(fā)送消息成功,數(shù)據(jù)庫操作失敗。
那么現(xiàn)在來看第二種方案,先進行數(shù)據(jù)庫操作,再發(fā)送消息:
begin transaction:// 1. 數(shù)據(jù)庫操作// 2. 發(fā)送MQ commit transaction;- 1
- 2
- 3
- 4
這種情況下貌似沒有問題,如果發(fā)送MQ消息失敗,就會拋出異常,導(dǎo)致數(shù)據(jù)庫事務(wù)回滾。但如果是超時異常,數(shù)據(jù)庫回滾,但MQ其實已經(jīng)正常發(fā)送了,同樣會導(dǎo)致不一致。
1.2 事務(wù)參與方接收消息的可靠性
事務(wù)參與方必須能夠從消息隊列接收到消息,如果接收消息失敗可以重復(fù)接收消息。
1.3 消息重復(fù)消費的問題
由于網(wǎng)絡(luò)2的存在,若某一消費節(jié)點超時但是消費成功,此時消息中間件會重復(fù)投遞此消息,就導(dǎo)致了消息的重復(fù)消費。
要解決消息重復(fù)消費的問題就要實現(xiàn)事務(wù)參與方的方法冪等性。
2. 解決方案
2.1 本地消息表方案
本地消息方案是通過本地事務(wù)保證數(shù)據(jù)業(yè)務(wù)操作和消息的一致性,然后通過定時任務(wù)將消息發(fā)送至消息中間件,待確認(rèn)消息發(fā)送給消費方成功再將消息刪除。
以注冊送積分為例來說明:
一共有兩個微服務(wù)交互,用戶服務(wù)和積分服務(wù),用戶服務(wù)負責(zé)添加用戶,積分服務(wù)負責(zé)添加積分:
交互流程如下:
2.1.1 用戶注冊
用戶服務(wù)在本地事務(wù)新增用戶增加”積分消息日志“。(用戶表和消息表通過本地事務(wù)保證一致)
下邊是偽代碼
begin transaction;// 1. 新增用戶// 2. 存儲積分消息日志 commit transaction;- 1
- 2
- 3
- 4
這種情況下,本地數(shù)據(jù)庫操作與存儲積分消息日志處于同一事務(wù)中,本地數(shù)據(jù)庫操作與記錄消息日志操作具備原子性。
2.1.2 定時任務(wù)掃描日志
如何保證將消息發(fā)送給消息隊列呢?
經(jīng)過第一步消息已經(jīng)寫到消息日志表中,可以啟動獨立的線程,定時對消息日志表中的消息進行掃描并發(fā)送至消息中間件,在消息中間件反饋成功后刪除該消息日志,否則等待定時任務(wù)下一周期重試。
2.1.3 消費消息
如何保證消費者一定能消費到消息呢?
這里可以使用MQ的ACK(即消息確認(rèn))機制,消費者監(jiān)聽MQ,如果消費者接受到消息并且業(yè)務(wù)處理完成后MQ發(fā)送ACK(即消息確認(rèn)),此時說明消費者正常消費消息完成,MQ將不再向消費者推送消息,否則消費者會不斷重試向消費者來發(fā)送消息。
積分服務(wù)接收到”增加積分“消息,開始增加積分,積分增加成功后向消息中間件回應(yīng)ACK,否則消息中間件將重復(fù)投遞此消息。
由于消息會重復(fù)投遞,積分服務(wù)的”增加積分“功能需要實現(xiàn)冪等性。
2.2 RocketMQ事務(wù)消息方案
RocketMQ是一個來自阿里巴巴的分布式消息中間件。RocketMQ事務(wù)消息設(shè)計則主要為了解決Producer端的消息發(fā)送與本地事務(wù)執(zhí)行的原子性問題,RocketMQ設(shè)計中broker與producer端的雙向通信能力,使得broker天生可以作為一個事務(wù)協(xié)調(diào)者存在;而RocketMQ本身提供的存儲機制為事務(wù)消息提供了持久化能力;RocketMQ的高可用機制以及可靠消息設(shè)計則為事務(wù)消息在系統(tǒng)發(fā)生異常時依然能夠保證達成事務(wù)的最終一致性。
在RocketMQ 4.3 后實現(xiàn)了完整的事務(wù)消息,實際上其實是對本地消息表的一個封裝,將本地消息表移動到了MQ內(nèi)部,解決了 Producer 端的消息發(fā)送與本地事務(wù)執(zhí)行的原子性問題。
執(zhí)行流程如下:
我們還以注冊送積分的例子來描述整個流程。
Producer即MQ發(fā)送方,本例中是用戶服務(wù),負責(zé)新增用戶。MQ訂閱方即消息消費方,本例中是積分服務(wù),負責(zé)新增積分。
Producer發(fā)送事務(wù)消息
Producer(MQ發(fā)送方)發(fā)送事務(wù)消息至MQ Server,MQ Server將消息狀態(tài)標(biāo)記為 Prepared(預(yù)備狀態(tài)),注意此時這條消息消費者(MQ訂閱方)是無法消費到的。
本例中,Producer發(fā)送”增加積分消息“到 MQ Server。
MQ Server回應(yīng)消息發(fā)送成功
MQ Server接收到Producer發(fā)送給的消息則回應(yīng)發(fā)送表示MQ已接受到消息。
Producer執(zhí)行本地事務(wù)
Producer端執(zhí)行業(yè)務(wù)代碼邏輯,通過本地數(shù)據(jù)庫事務(wù)控制。本例中,Producer執(zhí)行添加用戶操作。
消息投遞
若Producer本地事務(wù)執(zhí)行成功功則自動向MQServer發(fā)送commit消息,MQ Server接收到commit消息后將”增加積 分消息“ 狀態(tài)標(biāo)記為可消費,此時MQ訂閱方(積分服務(wù))即正常消費消息;
若Producer 本地事務(wù)執(zhí)行失敗則自動向MQServer發(fā)送rollback消息,MQ Server接收到rollback消息后 將刪 除”增加積分消息“ 。
MQ訂閱方(積分服務(wù))消費消息,消費成功則向MQ回應(yīng)ack,否則將重復(fù)接收消息。這里ack默認(rèn)自動回應(yīng),即 程序執(zhí)行正常則自動回應(yīng)ack。
事務(wù)回查
如果執(zhí)行Producer端本地事務(wù)過程中,執(zhí)行端掛掉,或者超時,MQ Server將會不停的詢問同組的其他 Producer 來獲取事務(wù)執(zhí)行狀態(tài),這個過程叫事務(wù)回查。MQ Server會根據(jù)事務(wù)回查結(jié)果來決定是否投遞消息。
以上主干流程已由RocketMQ實現(xiàn),對用戶側(cè)來說,用戶需要分別實現(xiàn)本地事務(wù)執(zhí)行以及本地事務(wù)回查方法,因此 只需關(guān)注本地事務(wù)的執(zhí)行狀態(tài)即可。
RoacketMQ提供RocketMQQLocalTransactionListener接口:
public interface RocketMQLocalTransactionListener {/** ‐ 發(fā)送prepare消息成功此方法被回調(diào),該方法用于執(zhí)行本地事務(wù) ‐ @param msg 回傳的消息,利用transactionId即可獲取到該消息的唯一Id ‐ @param arg 調(diào)用send方法時傳遞的參數(shù),當(dāng)send時候若有額外的參數(shù)可以傳遞到send方法中,這里能獲取到 ‐ @return 返回事務(wù)狀態(tài),COMMIT:提交 ROLLBACK:回滾 UNKNOW:回調(diào) */RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg); /** ‐ @param msg 通過獲取transactionId來判斷這條消息的本地事務(wù)執(zhí)行狀態(tài) ‐ @return 返回事務(wù)狀態(tài),COMMIT:提交 ROLLBACK:回滾 UNKNOW:回調(diào) */ RocketMQLocalTransactionState checkLocalTransaction(Message msg); }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
發(fā)送事務(wù)消息:以下是RocketMQ提供用于發(fā)送事務(wù)消息的API:
TransactionMQProducer producer = new TransactionMQProducer("ProducerGroup"); producer.setNamesrvAddr("127.0.0.1:9876"); producer.start(); // 設(shè)置TransactionListener實現(xiàn) producer.setTransactionListener(transactionListener); // 發(fā)送事務(wù)消息 SendResult sendResult = producer.sendMessageInTransaction(msg, null);- 1
- 2
- 3
- 4
- 5
- 6
- 7
六、最大努力通知
1. 最大努力通知介紹
最大努力通知也是一種解決分布式事務(wù)的方案,下面以一個充值的案例舉例:
交互流程:
賬戶系統(tǒng)調(diào)用充值系統(tǒng)接口
充值系統(tǒng)完成支付處理向賬戶系統(tǒng)發(fā)起充值結(jié)果通知若通知失敗,則充值系統(tǒng)按策略進行重復(fù)通知
賬戶系統(tǒng)接收到充值結(jié)果通知修改充值狀態(tài)。
賬戶系統(tǒng)未接收到通知會主動調(diào)用充值系統(tǒng)的接口查詢充值結(jié)果。
通過上邊的例子我們總結(jié)最大努力通知方案的目標(biāo):
目標(biāo):發(fā)起通知方通過一定的機制最大努力將業(yè)務(wù)處理結(jié)果通知到接收方。
具體包括:
有一定的消息重復(fù)通知機制
因為接收通知方可能沒有接收到通知,此時要有一定的機制對消息重復(fù)通知;
消息校對機制
如果盡最大努力也沒有通知到接收方,或者接收方消費消息后要再次消費,此時可由接收方主動向通知方查詢消息來滿足需求。
最大努力通知與可靠消息一致性有什么不同?
解決方案思想不同
可靠消息一致性,發(fā)起通知方需要保證將消息發(fā)出去,并且將消息發(fā)到接收通知方,消息的可靠性關(guān)鍵由發(fā)起通知 方來保證。
最大努力通知,發(fā)起通知方盡最大的努力將業(yè)務(wù)處理結(jié)果通知為接收通知方,但是可能消息接收不到,此時需要接收通知方主動調(diào)用發(fā)起通知方的接口查詢業(yè)務(wù)處理結(jié)果,通知的可靠性關(guān)鍵在接收通知方。
兩者的業(yè)務(wù)應(yīng)用場景不同
可靠消息一致性關(guān)注的是交易過程的事務(wù)一致,以異步的方式完成交易。
最大努力通知關(guān)注的是交易后的通知事務(wù),即將交易結(jié)果可靠的通知出去。
技術(shù)解決方向不同
可靠消息一致性要解決消息從發(fā)出到接收的一致性,即消息發(fā)出并且被接收到。
最大努力通知無法保證消息從發(fā)出到接收的一致性,只提供消息接收的可靠性機制。可靠機制是,最大努力的將消 息通知給接收方,當(dāng)消息無法被接收方接收時,由接收方主動查詢消息(業(yè)務(wù)處理結(jié)果)。
2. 解決方案
通過對最大努力通知的理解,采用MQ的ack機制就可以實現(xiàn)最大努力通知。
方案1:
本方案是利用MQ的ack機制由MQ向接收通知方發(fā)送通知,流程如下:
使用普通消息機制將通知發(fā)給MQ。
注意:如果消息沒有發(fā)出去可由接收通知方主動請求發(fā)起通知方查詢業(yè)務(wù)執(zhí)行結(jié)果。(后邊會講)
接收通知方監(jiān)聽 MQ。
接收通知方接收消息,業(yè)務(wù)處理完成回應(yīng)ack。
接收通知方若沒有回應(yīng)ack則MQ會重復(fù)通知。
MQ會按照間隔1 min、5 min、10 min、30 min、1 h、2 h、5 h、10 h的方式,逐步拉大通知間隔(如果 MQ 采用rocketMQ,在broker中可進行配置),直到達到通知要求的時間窗口上限。
5、接收通知方可通過消息校對接口來校對消息的一致性。
方案2:
本方案也是利用MQ的ack機制,與方案1不同的是應(yīng)用程序向接收通知方發(fā)送通知,如下圖:
交互流程如下:
發(fā)起通知方將通知發(fā)給MQ。
使用可靠消息一致方案中的事務(wù)消息保證本地事務(wù)與消息的原子性,最終將通知先發(fā)給MQ。
通知程序監(jiān)聽 MQ,接收MQ的消息。
方案1中接收通知方直接監(jiān)聽MQ,方案2中由通知程序監(jiān)聽MQ。通知程序若沒有回應(yīng)ack則MQ會重復(fù)通知。
通知程序通過互聯(lián)網(wǎng)接口協(xié)議(如http、webservice)調(diào)用接收通知方案接口,完成通知。
通知程序調(diào)用接收通知方案接口成功就表示通知成功,即消費MQ消息成功,MQ將不再向通知程序投遞通知消 息。
接收通知方可通過消息校對接口來校對消息的一致性。
方案1和方案2的不同點:
方案1中接收通知方與MQ接口,即接收通知方案監(jiān)聽 MQ,此方案主要應(yīng)用與內(nèi)部應(yīng)用之間的通知。
方案2中由通知程序與MQ接口,通知程序監(jiān)聽MQ,收到MQ的消息后由通知程序通過互聯(lián)網(wǎng)接口協(xié)議調(diào)用接收 通知方。此方案主要應(yīng)用于外部應(yīng)用之間的通知,例如支付寶、微信的支付結(jié)果通知。
資料收集于網(wǎng)絡(luò)。
總結(jié)
以上是生活随笔為你收集整理的【转】分布式事务的常见解决方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 欧拉系装机量累计超170万套:支持X86
- 下一篇: 【转】PE文件结构详解--(完整版)