我還不懂什麼是分佈式事務
老大:來,你搞一搞分佈式事務吧
我:......, 啥是事務?
我:先從理論學起吧
我不懂什麼是事務
如果事務都不懂,就更不用說分佈式事務了,於是我馬上開始學習了。
事務是應用程序中一系列嚴密的操作,所有操作必須成功完成,否則在每個操作中所作的所有更改都會被撤消。
事務應該具有 4 個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱爲 ACID 特性。
換成比較容易理解的話就是,就是一組操作比如增刪改查四個操作要麼都成功,要麼都失敗,不存結果不一致的狀態。
我不懂什麼是分佈式事務
終於弄明白什麼是事務了,又來了分佈式事務。爲什麼需要分佈式事務呢?
事務更多指的是單機版、單數據庫的概念。分佈式事務 指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位於不同的分佈式系統的不同節點之上 。
換成比較容易理解的話,就是多個事務之間再保持事務的特性,也就是多個事務之間保證結果的一致性。
XA 規範
有了分佈式事務的場景,就會有解決該問題的方式規範,XA 規範就是解決分佈式事務的規範,具體描述見維基百科解釋:
XA 規範提供了一種重要思想:
1、引入全局事務的控制節點,事務的協調者
2、多個本地事務劃分多階段提交(也就是下面講的 2PC,3PC)
我不懂分佈式方案
有了規範就會有落地方案,下面介紹基於 XA 規範的幾個實現協議。
首先介紹兩階段提交 (Two-phase Commit) 和三階段提交( Three-phase Commit )
2PC(Two-phase Commit)
兩階段提交,顧名思義就是要分兩步提交。
這裏第一階段稱爲準備或者投票階段。引入一個負責協調各個本地資源管理器的事務管理器,
本地資源管理器一般是由數據庫實現,事務管理器在第一階段的時候詢問各個資源管理器是否都就緒,並執行完除提交事務外所有事情,然後把結果返回給事務協調者。
如果收到每個資源的回覆都是 成功,則在第二階段提交事務,如果其中任意一個資源的回覆是 失敗, 則回滾事務。
這裏的實現方式和我們平常開黑玩遊戲時差不多,當我們組隊時,隊長會讓大家準備,讓隊員上完廁所喫飽飯,如果所有隊員都準備好,那就開始遊戲,如果有任一一個隊員沒有喫飽,沒有確認準備好,就不會開始遊戲。
但是這種協議也會存在一些問題,如下:
同步阻塞,這是 2PC 最大的問題, 嚴格的 2PC 執行過程中,所有參與節點都是事務阻塞型的。當參與者佔有公共資源時,其他第三方節點訪問公共資源不得不處於阻塞狀態
解決方案:引入引入超時機制,如果長時間沒有收到響應,執行特定的動作。
協調者單點故障,協調者在 2PC 中是最重要的角色,同時也意味着如果他出問題,整個過程就 GG 了
解決方案:單點故障的常規方案就引入副本然後當主節點掛掉後,重新選主,就像組隊遊戲中,如果隊員都準備好後,隊長長時間蹲廁所不開始遊戲,遊戲程序一般就會踢掉隊長,其他組員切換成隊長身份。
數據不一致,雖然解決了上面幾個問題,但是由於分佈式系統存在很多網絡抖動和調用失敗場景還是會有數據不一致的情況,下面分爲協調者、參與者、網絡等故障來詳細分析一下:
1、協調者發送準備命令前掛掉
這種相當於事務直接沒有開始,沒有啥太大影響
2、協調者發送準備命令後掛掉
這種情況,如果參與者沒有超時機制,就會造成資源鎖定
3、協調者發送提交命令前掛掉
這種情況和上一種情況類似,也會造成資源鎖定
4、協調者發送提交命令後掛掉
這種情況很可能是能夠成功執行分佈式事務的,因爲已經到了提交階段說明其他參與者都已經準備好,如果失敗就不斷重試
5、協調者發送回滾命令前掛掉
這種情況和 2、3 是類似的,由於參與者收不到執行操作的命令,如果沒有超時會一直阻塞並佔據着資源
6、協調者發送回滾命令後掛掉
這種情況和 4 差不多,也是很大概率是能夠成功執行回滾事務的,如果沒有成功,由於已經形成了決議,所以只能不斷重試
7、協調者發送準備命令後,部分參與者掛掉
這種情況協調者有超時機制,直接判定成失敗,然後通知所有參與者回滾
8、協調者發送準備命令後掛掉,且部分參與者掛掉
這種情況重新選舉協調者後,發現還在第一階段,由於沒有收到掛掉參與者的響應,所以判定失敗,通知其他參與者執行回滾
9、協調者發送提交或回滾命令後掛掉,且收到消息的參與者掛掉
這種情況重新選舉協調者後,沒有收到消息的參與者沒有執行事務,但是協調者無法確定收到消息的參與者執行第二階段的提交或回滾到底是否成功,就會出現事務不一致的情況
3PC(Three-phase Commit)
從上面介紹的相關內容也可以大體知道 2PC 的缺點和解決方式,於是就有了下面的解決協議,三階段提交
從百科可以看到 3PC 的引入主要就是爲了解決上面我們說的 2PC 的缺點,咋就能解決呢?
1、3PC 是非阻塞協議
好的,就是爲了解決了資源佔用問題,主要也就是引入了參與者超時機制
2、 第一階段與第二階段之間插入了一個準備階段
解決了在兩階段提交中,參與者在投票之後,由於協調者發生崩潰或錯誤,而導致參與者處於無法知曉是否提交或者回滾的 “不確定狀態”,也就是爲了保證最後提交階段之前所有參與節點狀態一致
3PC 把 2PC 第一階段再次拆分爲 2 個階段,多了一個階段其實就是在執行事務之前來確認參與者是否正常,防止個別參與者不正常的情況下,其他參與者都執行了事務鎖定資源。
他的大概步驟其實可以按照參與者 4 個狀態來劃分
0、初始狀態,此階段事務發起者觸發全局事務,參與者切換本地狀態爲開始狀態,並把自己註冊到協調者中。
1、可提交或狀態等待,此階段協調者發送命令到每個註冊過來的參與者,讓他們更改狀態爲可提交狀態。
2、預提交狀態,此階段協調者收到參與者確認可以提交併進入狀態,然後協調者向他們發送預提交消息,參與者鎖定資源,並更改狀態爲預提交狀態。同時 協調者也進入預提交狀態。
3、提交狀態,此階段協調者根據參與者預提交的結果執行提交或回滾操作,然後釋放資源。
通過這種方式可以解決一些 2PC 狀態不一致問題。JBoss 上大佬的總結:
大概意思是,通過引入預提交階段,協調者能夠確定參與者提交前的狀態,同時參與者也能夠推斷其他參與者狀態
協調者正常的情況下,可以根據參與者狀態切換的結果來決定是執行還是回滾。多出的一個預提交階段就是爲了統一狀態。
參與者如果沒有收到協調者消息,會默認執行提交,雖然可能會導致數據不一致。
協調者掛掉重新選舉後,會根據參與者和原主節點狀態確定是執行還是回滾。
新協調者來的時候發現自己是可提交狀態並且參與者爲可提交和回滾狀態,說明經過投票回滾的,此時新協調者執行回滾命令
新協調者來的時候發現自己是預提交併且參與者處於預提交和提交狀態,那麼表明已經經過了所有參與者的確認了,所以此時執行的就是提交命令
可以看到 3PC 由於多引入了一個階段,性能會比較低,而且其實也沒有解決數據一致性問題,多了一個階段的效果也不能保證效果一定要比 2PC 要好,所以一般還是很少用。
TCC(Try-Confirm-Cancel)
2PC/3PC 模式基於 支持本地 ACID 事務 的 關係型數據庫:
-
一階段 prepare 行爲:在本地事務中,一併提交業務數據更新和相應回滾日誌記錄。
-
二階段 commit 行爲:馬上成功結束,自動 異步批量清理回滾日誌。
-
二階段 rollback 行爲:通過回滾日誌,自動 生成補償操作,完成數據回滾。
相應的,TCC 模式從業務層面處理,不依賴於底層數據資源的事務支持:
-
一階段 prepare 行爲:調用 自定義 的 prepare 邏輯。
-
二階段 commit 行爲:調用 自定義 的 commit 邏輯。
-
二階段 rollback 行爲:調用 自定義 的 rollback 邏輯。
所謂 TCC 模式,是指支持把 自定義 的分支事務納入到全局事務的管理中,可以不依賴本地數據庫,當然實現上可以依賴,更多的場景還是兩者結合。
TCC 更多的是讓業務來實現兩階段提交的思想,對業務侵入性大
Try 階段定義爲執行資源的鎖定,這個階段我認爲比較難實現,常規的思路是
轉賬場景時可能需要把嘗試賬戶餘額是否足夠,然後減去轉賬金額並把金額存入到臨時字段,做到鎖定金額
緩存場景可能就需要使用分佈式鎖,鎖定住要操作的緩存值,或者取出某個緩存到另一個緩存
上傳下載場景可能需要把文件存到服務器臨時目錄
Confirm 階段定義爲執行 try 階段鎖定的資源,也就是說基於 try 的成功,可以繼續操作,比如執行真正的轉賬、緩存操作、上傳下載等。
Cancel 階段定義爲釋放 Try 預留的資源,也就是說由於 Try 的失敗,需要作出相應的補償操作或者恢復環境,比如刪除掉轉賬時的臨時字段、釋放掉鎖、清理臨時文件等。
TCC 模式實現難度還是蠻大的,需要考慮很多異常場景,還要考慮資源如何鎖定和釋放,但是由於不會阻塞資源,應用方面也更廣,據說還是有很多公司熱衷於這種補償型的事務實現方式
還有就是這裏所說的 TCC 更多是一種思想,實際實現可能還是需要根據具體業務來做相應的調整,方法是死的,人是活的。
SAGA
理論基礎 (點擊查看原論文):Hector & Kenneth 發表論文 Sagas (1987)
Saga 模式提供的是長事務解決方案,在 Saga 模式中,業務流程中每個參與者都提交本地事務,當出現某一個參與者失敗則補償前面已經成功的參與者,一階段正向服務和二階段補償服務都由業務開發實現。
適用場景:
-
業務流程長、業務流程多
-
參與者包含其它公司或遺留系統服務,無法提供 TCC 模式要求的三個接口
Saga 主要思想是依賴於狀態機轉換,長事務拆分成多個短事務,依次執行短事務
如果某個短事務失敗,則按照前面執行順序的逆序執行補償事務
這種模式還少使用的,實現也是比較複雜,同時流程很長,當遇到類似場景時還是需要仔細考慮是否有必要去實現分佈式事務呢?
本地消息表
執行業務的時候 將業務的執行和將消息放入消息表中的操作放在同一個事務中,這樣就能保證消息放入本地表中業務肯定是執行成功的。
然後再去調用下一個服務,如果成功了,消息表的消息狀態可以直接改成已成功。
如果調用失敗,會有 後臺任務定時去讀取本地消息表,篩選出還未成功的消息再調用對應的服務,服務更新成功了再變更消息的狀態。
一般也會有重試次數限制,超出後執行回滾或者通知人工介入。
可見本地消息表也會出現數據不一致的情況,儘量保證最終一致性。
消息隊列
此方案的意思是通過支持事務的消息隊列來實現分佈式事務。
主要流程:
-
生產者發送半事務消息到 MQ
-
生產者收到 MQ 成功接收到之後,去執行本地事務,但是事務還沒有提交。
-
生產者會根據事務的執行結果來決定發送提交或者回滾到消息
-
生產者需要提供一個查詢事務狀態接口,如果一段時間內半消息沒有收到任何操作請求,那麼 MQ 會通過查詢接口獲得發送方事務執行結果。
-
如果是失敗結果的消息,MQ 直接丟棄,也就不會影響到消費者
-
如果是成功結果的消息,消費者消費半事務消息,然後再去消費普通消息
該方案與本地消息不同點是去掉了本地消息表,本地事務和 MQ 事務綁定在一起。目前市面上實現該方案的應該只有阿里的 RocketMq
最大努力通知
這種方式請進行最大努力自行學習吧
我不懂怎麼實現
學了這麼多方案,自己實現還是很有難度。
常見的解決方案的實現框架有:byteTCC 、華爲 ServiceComb 實現的 DTM(華爲 cloud 官網可見)、阿里 seata(收費版爲 GTS)、騰訊 DTF
目前開源最火的還是 seata,支持模式多、官網文檔詳細,這裏就不一一介紹了
關於 seata 的文章非常多,下篇文章也打算以 seata 框架實踐分佈式事務。
那 seata 是不是就完美了呢?當然不是,以後可能改進的幾點
1、不支持控制檯,沒有可視化界面,驗證全靠打印和連接數據庫
2、seata-server 高可用不支持 Raft 協議,事務信息完全依賴於 DB、redis 等
3、undoLog 佔用空間過大尤其是前後置鏡像一個大 JSON 字段,數據量大時可能會入庫慢,可能需要進行壓縮
4、只能通過異常回滾,不支持類似 Spring 的 Rollback-Only 標誌位回滾
5、全局鎖的粒度是不是有點大,分支事務是否有必要上報狀態到 TC
找到一份 seata 開源作者 jimin slievrly 的分享視頻一起學習
如果需要視頻中 PPT 學習,公衆號內****回覆 seata 即可:
我懂了
本文按照完全沒接觸過事務的學習流程進行書寫,腦圖如下:
左邊是基礎,右邊是方案,如果你也在學習分佈式事務相關知識,可以參考。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Co2ic4Al0-JQLbkTBuRchQ