多數據源之間不使用分佈式事務實現異步最終一致性



數據庫dbA
表t1

數據庫dbB
表t2

目標,t1插入記錄時,同時保證t2也插入

假如使用分佈事務,非常簡單

開始分佈事務
...
insert into t1 ...
insert into t2 ...
提交分佈事務


不使用分佈事務要保證一致是無法直接實現的,比如

開始事務
...
insert into t1 ...
提交事務
如果成功,insert into t2 ...

看起來,只要dbB始終可靠似乎沒問題,
實際即便能保證dbB可靠且始終能插入,也還是存在問題.
比如,提交事務成功,此時程序崩潰或者斷電了,
那麼就沒機會執行t2插入了.



爲此我們需要兩個庫各增加一張消息表
dbA
msgToB(id int not null,s varchar)

dbB
msgFromA(id int not null,s varchar)

其中id是主鍵,s是消息內容,流程如下:


開始dbA事務
...
insert into t1 ...
給dbB發消息 insert into msgToB (id,s)values(...)
提交dbA事務


其中插入msgToB的id可以是自增量,也可以是額外獲得的唯一ID.
這樣保證只要t1插入記錄,msgToB就有對應的一條消息

然後另一個程序
循環
 讀dbA中msgToB到id,s
 開始dbB事務
   如果msgFromA中沒有id記錄
     將id插入msgFromA
     根據s中內容,insert into t2 ...
 
   如果msgFromA中已經存在,說明已經處理過,直接返回即可
 提交dbB事務
 如果事務成功,刪除msgToB中對應記錄

這樣事務保證處理且只處理一次消息,事務外如果未刪除
msgToB記錄,等到下次處理就會被自動刪除且不會被重複處理.

這樣達到我們的目標,兩個獨立的庫通過額外的兩張表用異步消息的機制達成了最終一致性.

當然這裏有一點是需要應用注意的,就是在dbB中的事務中
如果插入t2失敗(比如已經存在記錄),那麼對應消息將永遠無法處理,
在單數據事務中,t2出錯時,數據庫會回滾對t1的插入.
一般這也是我們期望的.然而此種機制則需要應用來解決這個問題,
比如如果處理反覆失敗,則標記這個消息已經處理,並給dba發送一條新消息,
讓dbA來處理這種狀態,比如撤銷對t1的插入.

更通常的做法應該是由應用保證類似情況不會發生,
既然我們已經設計了兩個獨立的數據源僅僅依靠異步消息來連接,
那麼就必須考慮所有的正常流程與回滾方法.
比如:
單機裏
1.下單,插入訂單表
2.扣倉庫庫存
3.庫存不夠,回滾

在一個事務裏,用戶下單後,系統可以立刻告訴他沒庫存了,
這時根本不會生成新訂單(事務中被回滾了).

如果分佈處理
1.訂單庫,用戶下單
此時訂單已經生成了

2.訂單轉到倉庫1
3.倉庫1意外發現無貨了(比如剛發現貨被摔爛了...)
4.倉庫1發消息給訂單庫
5.訂單庫將訂單狀態修改成無貨取消
然後可以再給另外的支付庫發消息,完成退款

這裏,訂單庫,與倉庫1不光邏輯上是不同數據庫而且物理上
可能相隔甚遠,兩者網絡可能高延時,低帶寬,不穩定(比如星淘,在另外一個星球上...)
這時兩階段提交的分佈事務幾乎無可能,
這種分佈式異步處理最終強一致性系統可能是唯一的選擇.



擴展一下:
1.任何一個支持本地事務的數據源都可以參與這種事務.
比如一個數據庫可以和redis或者兩個redis之間.
因爲雖然redis不支持分佈式事務,但是內部卻有事務機制,
只要在雙方增加一個類似的消息表.

2.分佈式水平擴展
比如dba實際可以對應dbb0,dbb1,dbb2...
可以很簡單的根據t1的主鍵將數據分佈到不同數據庫的t2表而實現水平擴展.


3.通過級聯,可以將一個事務擴展到更多不同節點.
比如,數據源1發消息給數據源2,數據源2處理後發給數據源3...

開始數據源2事務
...
完成數據源1的消息處理,insert into msgFrom1
給數據源3發消息,insert into msgTo3

提交數據源2事務

結合充分理解和使用單機事務,你會發現這種機制非常簡單好用。

4.與直接使用消息隊列系統的比較
消息隊列已經在現代大型IT系統廣泛採用了.
一種是弱事務性的處理,允許丟或多消息,這種方式與我們上面要求的強一致性應用場景完全不同.
另一種也要求消息絕對不能丟,這種場景與上面討論的一致.
但是要注意到,必須使用兩階段提交才能保證不丟消息!
即便消息隊列本身可以保證.

比如發送消息,如果一個保證可靠消息的消息系統告訴我們成功了,
我們可以肯定這個消息不會丟失,接受者肯定可以接受到,
但是接受到不代表能處理!比如接收方

開始消息事務
讀消息
 開始數據庫事務
   處理消息
 提交數據庫事務
刪除消息
提交消息事務

問題很顯然,提交數據庫事務後,消息實際被處理了,
但是此時系統崩潰的話,消息將不會被刪除,還會被再處理一遍.
反之如果先提交消息事務,隨後崩潰的話,就會導致消息丟失.
解決方式就是要麼將消息事務與數據庫事務合併爲分佈式事務,
要麼同樣採用上面的方式,在兩個數據源增加表.
而採用後種方式時,消息隊列的優勢就喪失了(因爲使用消息系統
一般就是看中她快捷,如果在插入消息隊列時還要額外再插入數據庫
消息系統還有什麼存在的必要呢?).

從這裏可以看出,消息隊列系統實際最適合異步弱一致性處理,
類似處理日誌這種,偶爾多個少個幾個完全無所謂.
如果看重他的其他方面但還要求消息絕對可靠,
那麼在和其他系統連接時,必須使用分佈式事務.
不然長長的管子不漏,接頭漏了,還是白忙活.




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章