1. 程式人生 > >分散式事務--補償模式+Mq/TCC

分散式事務--補償模式+Mq/TCC

個人備忘

個人理解也是閱讀別人博文,自己整理了一下,希望對你們有幫助。

補償模式

MQ(事務訊息) :

舉個例子,Bob向Smith轉賬,那我們到底是先發送訊息,還是先執行扣款操作?

好像都可能會出問題。如果先發訊息,扣款操作失敗,那麼Smith的賬戶裡面會多出一筆錢。反過來,如果先執行扣款操作,後傳送訊息,那有可能扣款成功了但是訊息沒發出去,Smith收不到錢。除了上面介紹的通過異常捕獲和回滾的方式外,還有沒有其他的思路呢?

下面以阿里巴巴的RocketMQ中介軟體為例,分析下其設計和實現思路。

RocketMQ第一階段傳送Prepared訊息時,會拿到訊息的地址,第二階段執行本地事物,第三階段通過第一階段拿到的地址去訪問訊息,並修改狀態。細心的讀者可能又發現問題了,如果確認訊息傳送失敗了怎麼辦?RocketMQ會定期掃描訊息叢集中的事物訊息,這時候發現了Prepared訊息,它會向訊息傳送者確認,Bob的錢到底是減了還是沒減呢?如果減了是回滾還是繼續傳送確認訊息呢?RocketMQ會根據傳送端設定的策略來決定是回滾還是繼續傳送確認訊息。這樣就保證了訊息傳送與本地事務同時成功或同時失敗。如下圖:

這裡寫圖片描述

總結:據筆者的瞭解,各大知名的電商平臺和網際網路公司,幾乎都是採用類似的設計思路來實現“最終一致性”的。這種方式適合的業務場景廣泛,而且比較可靠。不過這種方式技術實現的難度比較大。目前主流的開源MQ(ActiveMQ、RabbitMQ、Kafka)均未實現對事務訊息的支援,所以需二次開發或者新造輪子。比較遺憾的是,RocketMQ事務訊息部分的程式碼也並未開源,需要自己去實現。

MQ(非事務訊息):

通常情況下,在使用非事務訊息支援的MQ產品時,我們很難將業務操作與對MQ的操作放在一個本地事務域中管理。通俗點描述,還是以上述提到的“跨行轉賬”為例,我們很難保證在扣款完成之後對MQ投遞訊息的操作就一定能成功。這樣一致性似乎很難保證。

先從訊息生產者這端來分析,請看虛擬碼:

這裡寫圖片描述

根據上述程式碼及註釋,我們來分析下可能的情況:

  1.操作資料庫成功,向MQ中投遞訊息也成功,皆大歡喜
  2.操作資料庫失敗,不會向MQ中投遞訊息了
  3.操作資料庫成功,但是向MQ中投遞訊息時失敗,向外丟擲了異常,剛剛執行的更新資料庫的操作將被回滾

從上面分析的幾種情況來看,貌似問題都不大的。那麼我們來分析下消費者端面臨的問題:

  1.訊息出列後,消費者對應的業務操作要執行成功。如果業務執行失敗,訊息不能失效或者丟失。需要保證訊息與業務操作一致
  2.儘量避免訊息重複消費。如果重複消費,也不能因此影響業務結果

如何保證訊息與業務操作一致,不丟失?

主流的MQ產品都具有持久化訊息的功能。如果消費者宕機或者消費失敗,都可以執行重試機制的(有些MQ可以自定義重試次數)。

如何避免訊息被重複消費造成的問題?

1.保證消費者呼叫業務的服務介面的冪等性
2.通過消費日誌或者類似狀態表來記錄消費狀態,便於判斷(建議在業務上自行實現,而不依賴MQ產品提供該特性)

總結:這種方式比較常見,效能和吞吐量是優於使用關係型資料庫訊息表的方案。如果MQ自身和業務都具有高可用性,理論上是可以滿足大部分的業務場景的。不過在沒有充分測試的情況下,不建議在交易業務中直接使用。

其他補償方式

做過支付寶交易介面的同學都知道,我們一般會在支付寶的回撥頁面和接口裡,解密引數,然後呼叫系統中更新交易狀態相關的服務,將訂單更新為付款成功。同時,只有當我們回撥頁面中輸出了success字樣或者標識業務處理成功相應狀態碼時,支付寶才會停止回撥請求。否則,支付寶會每間隔一段時間後,再向客戶方發起回撥請求,直到輸出成功標識為止。

其實這就是一個很典型的補償例子,跟一些MQ重試補償機制很類似。

一般成熟的系統中,對於級別較高的服務和介面,整體的可用性通常都會很高。如果有些業務由於瞬時的網路故障或呼叫超時等問題,那麼這種重試機制其實是非常有效的。

當然,考慮個比較極端的場景,假如系統自身有bug或者程式邏輯有問題,那麼重試1W次那也是無濟於事的。那豈不是就發生了“明明已經付款,卻顯示未付款不發貨”類似的悲劇?

其實為了交易系統更可靠,我們一般會在類似交易這種高級別的服務程式碼中,加入詳細日誌記錄的,一旦系統內部引發類似致命異常,會有郵件通知。同時,後臺會有定時任務掃描和分析此類日誌,檢查出這種特殊的情況,會嘗試通過程式來補償並郵件通知相關人員。

在某些特殊的情況下,還會有“人工補償”的,這也是最後一道屏障。

綜上我們設計自己的分散式事務:

//案例
 1.訊息提供者確保傳送訊息無異常,否則rollback對資料庫的操作。
 2.消費端雖然有持久化儲存和重試機制,最好將訊息加入到java本地佇列中。
 3.消費端為單獨一個服務,輪詢監聽本地佇列中的訊息,執行核心業務。
 4.記錄日誌,異常後從新放到本地佇列中。根據具體業務設定補償次數等(可避免因bug 導致無限補償)。
 5.視業務需要,是否需要開發管理頁面進行人工在次對未完成業務的訊息進行補償。

小結:這樣即可保證提供者和消費者兩臺服務保證 “資料的一致性” 。

小結

上訴的幾種方案中,筆者也大致總結了其設計思路,優勢,劣勢等,相信讀者已經有了一定的理解。其實分散式系統的事務一致性本身是一個技術難題,目前沒有一種很簡單很完美的方案能夠應對所有場景。具體還是要使用者根據不同的業務場景去抉擇。

/***************************************************************************************************************/

TCC模式 :

TCC事務機制
1. TCC的機制
明眼一看就知道,TCC應該是三個英文單詞的首字母縮寫而來。沒錯,TCC分別對應Try、Confirm和Cancel三種操作,這三種操作的業務含義如下:Try:預留業務資源Confirm:確認執行業務操作Cancel:取消執行業務操作 稍稍對照下關係型資料庫事務的三種操作:DML、Commit和Rollback,會發現和TCC有異曲同工之妙。在一個跨應用的業務操作中,Try操作是先把多個應用中的業務資源預留和鎖定住,為後續的確認打下基礎,類似的,DML操作要鎖定資料庫記錄行,持有資料庫資源;Confirm操作是在Try操作中涉及的所有應用均成功之後進行確認,使用預留的業務資源,和Commit類似;而Cancel則是當Try操作中涉及的所有應用沒有全部成功,需要將已成功的應用進行取消(即Rollback回滾)。其中Confirm和Cancel操作是一對反向業務操作。

這裡寫圖片描述

簡而言之,TCC是應用層的2PC(2 Phase Commit, 兩階段提交),如果你將應用看做資源管理器的話。
詳細來說,TCC每項操作需要做的事情如下:Try:嘗試執行業務。完成所有業務檢查(一致性)預留必須業務資源(準隔離性)Confirm:確認執行業務。真正執行業務不做任何業務檢查只使用Try階段預留的業務資源Cancel:取消執行業務釋放Try階段預留的業務資源 用一張圖來說明TCC的機制:

這裡寫圖片描述

一個完整的TCC事務參與方包括三部分:主業務服務:主業務服務為整個業務活動的發起方,如前面提到的組合支付場景,支付系統即是主業務服務。從業務服務:從業務服務負責提供TCC業務操作,是整個業務活動的操作方。從業務服務必須實現Try、Confirm和Cancel三個介面,供主業務服務呼叫。由於Confirm和Cancel操作可能被重複呼叫,故要求Confirm和Cancel兩個介面必須是冪等的。前面的組合支付場景中的餘額系統和紅包系統即為從業務服務。
業務活動管理器:業務活動管理器管理控制整個業務活動,包括記錄維護TCC全域性事務的事務狀態和每個從業務服務的子事務狀態,並在業務活動提交時確認所有的TCC型操作的confirm操作,在業務活動取消時呼叫所有TCC型操作的cancel操作。
可見整個TCC事務對於主業務服務來說是透明的,其中業務活動管理器和從業務服務各自幹了一部分工作。
2. TCC的優點和限制
TCC事務的優點如下:解決了跨應用業務操作的原子性問題,在諸如組合支付、賬務拆分場景非常實用。TCC實際上把資料庫層的二階段提交上提到了應用層來實現,對於資料庫來說是一階段提交,規避了資料庫層的2PC效能低下問題。 TCC事務的缺點,主要就一個:
TCC的Try、Confirm和Cancel操作功能需業務提供,開發成本高。
當然,對TCC事務的這個缺點是否是缺點,是一個見仁見智的事情。

3. 一個案例理解TCC
說實話,TCC的理論有點讓人費解。故接下來將以賬務拆分為例,對TCC事務的流程做一個描述,希望對理解TCC有所幫助。
賬務拆分的業務場景如下,分別位於三個不同分庫的帳戶A、B、C,A和B一起向C轉帳共80元:

Try:嘗試執行業務。完成所有業務檢查(一致性):檢查A、B、C的帳戶狀態是否正常,帳戶A的餘額是否不少於30元,帳戶B的餘額是否不少於50元。預留必須業務資源(準隔離性):帳戶A的凍結金額增加30元,帳戶B的凍結金額增加50元,這樣就保證不會出現其他併發程序扣減了這兩個帳戶的餘額而導致在後續的真正轉帳操作過程中,帳戶A和B的可用餘額不夠的情況。Confirm:確認執行業務。真正執行業務:如果Try階段帳戶A、B、C狀態正常,且帳戶A、B餘額夠用,則執行帳戶A給賬戶C轉賬30元、帳戶B給賬戶C轉賬50元的轉帳操作。不做任何業務檢查:這時已經不需要做業務檢查,Try階段已經完成了業務檢查。只使用Try階段預留的業務資源:只需要使用Try階段帳戶A和帳戶B凍結的金額即可。Cancel:取消執行業務釋放Try階段預留的業務資源:如果Try階段部分成功,比如帳戶A的餘額夠用,且凍結相應金額成功,帳戶B的餘額不夠而凍結失敗,則需要對帳戶A做Cancel操作,將帳戶A被凍結的金額解凍掉。 小結:到底要不要使用TCC 到底要不要使用TCC事務,取決於以下幾點:是否真正有保證跨應用業務操作的原子性需求。研發上能否投入資源開發相對應的TCC介面。
當然還有最後一點,能否搞定一個穩定的、高可用的、擴充套件性強的TCC事務管理器。
一個問題,如果TCC事務在Try階段所有參與方(從業務服務)成功了,但是Confirm階段部分參與方(從業務服務)成功,如何處理?
TCC參考資料《大規模SOA系統中的分散式事務處理》。

冪等性 :

什麼是冪等性

抄用一段數學上的定義:f(f(x)) = f(x)。x被函式f作用一次和作用無限次的結果是一樣的。冪等性應用在軟體系統中,我把它簡單定義為:某個函式或者某個介面使用相同引數呼叫一次或者無限次,其造成的後果是一樣的,在實際應用中一般針對於介面進行冪等性設計。舉個栗子,在系統中,呼叫方A呼叫系統B的介面進行使用者的扣費操作時,由於網路不穩定,A重試了N次該請求,那麼不管B是否接收到多少次請求,都應該保證只會扣除該使用者一次費用。

//小編個人理解
我們主題是 “分散式事務” ,在我們的上述分散式幾種事務解決方案中,要保證冪等,我們可以通過日誌+資料庫鎖(其他分散式鎖也可)等來保證無論介面呼叫幾次,都只之執行一次,不會因為執行多次而改變真實的資料。