1. 程式人生 > >我還不懂什麼是分散式事務

我還不懂什麼是分散式事務

老大:來,你搞一搞分散式事務吧

我:......,啥是事務?

我:先從理論學起吧

我不懂什麼是事務

如果事務都不懂,就更不用說分散式事務了,於是我馬上開始學習了。

事務是應用程式中一系列嚴密的操作,所有操作必須成功完成,否則在每個操作中所作的所有更改都會被撤消。

事務應該具有 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主要思想是依賴於狀態機轉換,長事務拆分成多個短事務,依次執行短事務

如果某個短事務失敗,則按照前面執行順序的逆序執行補償事務

這種模式還少使用的,實現也是比較複雜,同時流程很長,當遇到類似場景時還是需要仔細考慮是否有必要去實現分散式事務呢?

本地訊息表

執行業務的時候 將業務的執行和將訊息放入訊息表中的操作放在同一個事務中,這樣就能保證訊息放入本地表中業務肯定是執行成功的。

然後再去呼叫下一個服務,如果成功了,訊息表的訊息狀態可以直接改成已成功。

如果呼叫失敗,會有 後臺任務定時去讀取本地訊息表,篩選出還未成功的訊息再呼叫對應的服務,服務更新成功了再變更訊息的狀態。

一般也會有重試次數限制,超出後執行回滾或者通知人工介入。

可見本地訊息表也會出現資料不一致的情況,儘量保證最終一致性。

訊息佇列

此方案的意思是通過支援事務的訊息佇列來實現分散式事務。

主要流程:

  1. 生產者傳送半事務訊息到MQ
  2. 生產者收到MQ成功接收到之後,去執行本地事務,但是事務還沒有提交。
  3. 生產者會根據事務的執行結果來決定傳送提交或者回滾到訊息
  4. 生產者需要提供一個查詢事務狀態介面,如果一段時間內半訊息沒有收到任何操作請求,那麼 MQ 會通過查詢介面獲得傳送方事務執行結果。
  5. 如果是失敗結果的訊息,MQ直接丟棄,也就不會影響到消費者
  6. 如果是成功結果的訊息,消費者消費半事務訊息,然後再去消費普通訊息

該方案與本地訊息不同點是去掉了本地訊息表,本地事務和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即可:

 

我懂了

本文按照完全沒接觸過事務的學習流程進行書寫,腦圖如下:

左邊是基礎,右邊是方案,如果你也在學習分散式事務相關知識,可以參考。

本文是系列第一篇,後面計劃一篇為seata實戰,一篇為seata原理和如何設計一個通用分散式事務框架。感謝閱讀,歡迎關