基於RocketMq的分散式事務解決方案

愛程式設計的碼神發表於2019-08-21

特邀作者:老顧

轉載請宣告出處!

前言

在系統變的複雜後,分散式、微服務等架構技術,就要考慮到應用在系統中了。尤其資料量大了後,就需要對資料庫進行拆分

如:註冊的使用者資料,量大了後,就需要考慮分庫分表

一旦資料庫進行了分拆,那就出現很多頭疼的問題,其中之一就是事務問題。那我們就來看看問題是怎麼出現的?

場景

先來上個圖

基於RocketMq的分散式事務解決方案

進行資料拆分後,就類似上面的架構,可以看老顧上一篇文章關於【Mysql的高可用架構】

上圖中我們就拿使用者的資料進行舉例,使用者量一旦幾千萬時,就需要進行分庫分表;上圖就分了3個庫,每個庫都保證了高可用。

這樣的架構設計,會遇到事務問題,我們來看看具體的業務場景:使用者A轉賬100元給使用者B,這個業務比較簡單,我們來分析一下里面具體的步驟

1、使用者A的賬戶先扣除100元 2、再把使用者B的賬戶加100元

邏輯很簡單,上虛擬碼

基於RocketMq的分散式事務解決方案

程式碼也是比較清晰的,感覺沒有什麼問題,那我們來分析一下問題在哪?

問題

我們看到在轉賬業務中,有兩步,一個是操作使用者A扣錢,一個是操作使用者B加錢

如果在同一個資料庫中進行,可以保證這兩步操作,要麼同時成功,要麼同時不成功。這樣就保證了轉賬的資料一致性。

但是如果使用者A的資料在叢集A中,使用者B在叢集B中呢?因為他們不在同一個事務中;如使用者A扣款成功,但使用者B加錢失敗了;那就坑了,資料不完整了。

類似這種問題在微服務架構會更多,因為各個服務都是獨立的模組,都是遠端呼叫,都沒法在同一個事務中,都會遇到事務問題。

那怎麼解決?網上有一些方案,如:兩階段提交,TCC等,還有常用就是最終一致性方案。老顧就給大家介紹一下如何利用訊息中介軟體去解決。那我們就把方案調整一下,加入訊息中介軟體,看看如何優化。

訊息中介軟體方案

基於RocketMq的分散式事務解決方案

上圖就是利用訊息中介軟體的方式,把扣款業務和加錢業務非同步化,扣款成功後,傳送“扣款成功訊息”到訊息中介軟體;加錢業務訂閱“扣款成功訊息”,再對使用者B加錢

系統怎麼知道給使用者B加錢呢?是訊息體裡面包含了源賬戶和目標賬戶ID,以及錢數

這個時候也許小夥伴們會問,應該也有問題吧:場景一:先扣款後發訊息

先扣款再傳送訊息,萬一傳送訊息失敗了,那使用者B就沒法加錢

那把順序調整一下場景二:先發訊息,後扣款

扣款成功訊息傳送成功,但使用者A扣款失敗,可加錢業務訂閱到了訊息,使用者B加了錢

大家應該發現了問題所在,也就是沒法保證扣款和傳送訊息,同時成功,或同時失敗;導致資料不一致。

RocketMq事務方案

因為上面的問題,RocketMq訊息中介軟體把訊息分為兩個階段Prepared階段確認階段、Prepared階段(預備階段)

該階段主要發一個訊息到rocketmq,但該訊息只儲存在commitlog中但consumeQueue中不可見,也就是消費端(訂閱端)無法看到此訊息

commit/rollback階段(確認階段)

該階段主要是把prepared訊息儲存到consumeQueue中,即讓消費端可以看到此訊息,也就是可以消費此訊息

我們用圖來說明下:

基於RocketMq的分散式事務解決方案

整個流程

1、在扣款之前,先傳送預備訊息2、傳送預備訊息成功後,執行本地扣款事務3、扣款成功後,再傳送確認訊息4、訊息端(加錢業務)可以看到確認訊息,消費此訊息,進行加錢

確認訊息說明

注意:上面的確認訊息可以為commit訊息,可以被訂閱者消費;也可以是Rollback訊息,即執行本地扣款事務失敗後,提交rollback訊息,即刪除那個預備訊息,訂閱者無法消費

我們來分析一下異常場景

異常1:如果傳送預備訊息失敗,下面的流程不會走下去;這個是正常的。異常2:如果傳送預備訊息成功,但執行本地事務失敗;這個也沒有問題,因為此預備訊息不會被消費端訂閱到,消費端不會執行業務。異常3:如果傳送預備訊息成功,執行本地事務成功,但傳送確認訊息失敗;這個就有問題了,因為使用者A扣款成功了,但加錢業務沒有訂閱到確認訊息,無法加錢。這裡出現了資料不一致。

那RocketMq是怎麼解決的呢?

RocketMq回查

基於RocketMq的分散式事務解決方案

RocketMq如何解決上面的問題,核心思路就是【狀態回查】,也就是RocketMq會定時遍歷commitlog中的預備訊息。

因為預備訊息最終肯定會變為commit訊息或Rollback訊息,所以遍歷預備訊息去回查本地業務的執行狀態,如果發現本地業務沒有執行成功就rollBack,如果執行成功就傳送commit訊息。

上面的異常3,傳送預備訊息成功,本地扣款事務成功,但傳送確認訊息失敗;因為RocketMq會進行回查預備訊息,在回查後發現業務已經扣款成功了,就補發“傳送commit確認訊息”;這樣加錢業務就可以訂閱此訊息了。

這個思路其實把異常2也解決了,因為本地事務沒有執行成功,RocketMQ回查業務,發現沒有執行成功,就會傳送RollBack確認訊息,把訊息進行刪除

回查判斷業務是否成功

小夥伴們在回查業務中,如何判斷本地事務是否執行成功

如果本地事務執行了很多張表,那是不是我們要把那些表都要進行判斷是否執行成功呢?這樣是不是太麻煩了,而且和業務很耦合。

有沒有更好的方式呢?就是設計一張Transaction表,將業務表和Transaction繫結在同一個本地事務中,如果扣款本地事務成功時,Transaction中應當已經記錄該TransactionId的狀態為「已完成」。當RocketMq回查時,只需要檢查對應的TransactionId的狀態是否是「已完成」就好,而不用關心具體的業務資料。

總結

上面就是老顧介紹的RockMq的分散式方案,至於消費端(加錢業務)需要考慮冪等設計,之前老顧的文章【何為冪等?如何設計?】有介紹,小夥伴自行查閱。

還有一點,留一個問題,如果我們不用RockMq訊息中介軟體,而是用普通的訊息中介軟體如:RabbitMq,這怎麼去設計呢?

好了,今天就介紹到這裡,謝謝!!!

喜歡的話,點個贊,關注我,還有更多技術乾貨分享~

相關文章