首先丟擲一個問題,如果在一臺機器上,資料庫是如何解決事務問題的?很容易想到,資料庫的ACID四個特性來保證的,原子性、一致性、隔離性和永續性。
拿mysql為例,資料先寫紀錄檔檔案,後寫資料檔案,如果寫紀錄檔檔案成功,並提交,發現資料檔案沒有,就做redo log,隨後做redo操作讓資料刷盤;如果紀錄檔檔案沒提交,需要寫undo log,用於回滾,AD特性是紀錄檔檔案保證的,CI特性是鎖保證的。
而在分散式系統中,事務是由多個系統對應的多個資料庫組成,涉及跨系統與跨庫操作,本地資料庫事務無能為力,需要引入分散式事務來保持資料的一致性。
下面給出本篇要討論的分散式事務的概覽,會重點分析幾種常見的實現方案與原理。
引入原因
兩階段也叫做2pc
,在分散式系統中,每個系統節點能感知自己的服務成功與失敗,但是無法感知其他節點的服務是否成功,就需要引入一個協調者
來掌控所有節點的操作結果,這些節點叫做參與者
,協調者控制所有節點的邏輯滿足ACID特性。
舉個例子來說,電商場景中,支付系統支付完成後,呼叫訂單系統更新訂單狀態。事務管理器Transaction Manager
簡稱TM
,充當協調者角色,Resource Manager
簡稱RS
,充當參與者角色。
流程
第一階段:預提交階段
第二階段:執行事務提交
以上是兩階段的正常流程,如果參與者在第一階段返回no,或者TM在第一階段詢問請求超時,無法獲得響應結果,事務就會中斷事務,並向所有參與者節點發起回滾請求,參與者節點利用之前寫的undo紀錄檔進行回滾,並釋放所佔資源;TM收到所有回滾完成結果後,取消事務。
二階段思想採用的是先投票(vote),後執行(do commit),典型的例子就是西式教堂裡的結婚場景,牧師詢問新郎新娘,你是否願意.....,當各自回答願意後(鎖定一生資源),牧師會宣佈:宣佈你們正式成為夫妻,......,正式結婚(事務提交)。
二階段的問題
seata實現
seata阿里開源的分散式事務,預設方式實現兩階段提交,流程如下:
mysql實現
mysql的redo log就是兩階段提交的典型例子,因為binlog屬於邏輯紀錄檔,redo log屬於物理紀錄檔,redo log紀錄檔保證修改的資料不丟失,可以基於紀錄檔恢復,而binlog記錄了做了哪些sql操作,兩個紀錄檔同時保證資料一致性。
從圖中可看出,事務的提交過程有兩個階段,就是將 redo log 的寫入拆成了兩個步驟:prepare 和 commit,中間再穿插寫入binlog,具體如下:
prepare 階段
:將 XID(內部 XA 事務的 ID) 寫入到 redo log,同時將 redo log 對應的事務狀態設定為 prepare,然後將 redo log 持久化到磁碟;
commit 階段
:把 XID 寫入到 binlog,然後將 binlog 持久化到磁碟,接著呼叫引擎的提交事務介面,將 redo log 狀態設定為 commit,此時該狀態並不需要持久化到磁碟,只需要 write 到檔案系統的 page cache 中就夠了,因為只要 binlog 寫磁碟成功,就算 redo log 的狀態還是 prepare 也沒有關係,一樣會被認為事務已經執行成功。
通過這種兩階段提交的方案,就能夠確保redo-log、bin-log兩者的紀錄檔資料是相同的。
為了降低二階段問題發生的概率,引入了三階段模型,也叫3PC
。三階段在二階段之前加入了詢問環節,詢問不鎖定資源
。3PC
包含了三個階段,分別是準備階段、預提交階段和提交階段,對應的英文就是:CanCommit、PreCommit 和 DoCommit。
三階段的改善
在分散式系統中,資料的狀態在多個系統中流轉,通過訊息佇列、定時任務與本地事件表可以有效的處理分散式事務的資料一致性。假設有如下場景,有支付系統與訂單系統兩個子系統,具體流程如下:
step1
: 支付系統通過第三方回撥完成本地支付流水狀態更新,同時在事件表中新增一條記錄;
step2
: 定時任務掃描事件表裡的新增記錄,將事件表裡的支付流水狀態更新為已傳送,然後傳送一條訊息到訊息中介軟體,如果傳送失敗,本地事務可以回滾;
step3
: 訂單系統中的消費者監聽訊息,將訊息與對應的狀態插入事件表;
step4
:定時任務讀取本地事務表,執行本地業務,更新訂單表狀態,最後將事件表的狀態再更新為終態。
優點
缺點
注意事項
生產環境中,如果多臺機器部署的話,需要考慮分散式定時任務,或者定時任務配合分散式鎖來操作,保證同一時刻只有一條記錄被定時掃描並執行。
背景
LCN框架在2017年6月份釋出第一個版本,從開始的1.0,已經發展到了5.0版本。
LCN名稱是由早期版本的LCN框架命名,在設計框架之初的1.0 ~ 2.0的版本時框架設計的步驟是如下,各取其首字母得來的LCN命名。
LCN全稱分別對應如下解釋:
鎖定事務單元(lock)
確認事務模組狀態(confirm)
通知事務(notify)
5.0以後由於框架相容了LCN、TCC、TXC三種事務模式,為了避免區分LCN模式,特此將LCN分散式事務改名為TX-LCN分散式事務框架。
TX-LCN由兩大模組組成, TxClient、TxManager,TxClient作為模組的依賴框架,提供TX-LCN的標準支援,TxManager作為分散式事務的控制方。事務發起方或者參與反都由TxClient端來控制。
核心步驟
是指在事務發起方開始執行業務程式碼之前先呼叫TxManager建立事務組物件,然後拿到事務標示GroupId的過程。
新增事務組是指參與方在執行完業務方法以後,將該模組的事務資訊通知給TxManager的操作。
是指在發起方執行完業務程式碼以後,將發起方執行結果狀態通知給TxManager,TxManager將根據事務最終狀態和事務組的資訊來通知相應的參與模組提交或回滾事務,並返回結果給事務發起方。
LCN事務模式
LCN模式是通過代理Connection的方式實現對本地事務的操作,然後在由TxManager統一協調控制事務。當本地事務提交回滾或者關閉連線時將會執行假操作,該代理的連線將由LCN連線池管理。
所以該模式的本質是:TM代理了資料來源機制,保持了請求與連線的對應關係。RM假釋放資源
,LCN並不生產事務,LCN只是本地事務的協調工。
如下圖:
假設服務已經執行到關閉事務組的過程,那麼接下來作為一個模組執行通知給TxManager,然後告訴他本次事務已經完成。
那麼如圖中Txmanager 下一個動作就是通過事務組的id,獲取到本次事務組的事務資訊;然後檢視一下對應有那幾個模組參與,如果是有A/B/C 三個模組;
那麼對應的對三個模組做通知:提交、回滾。
那麼提交的時候是提交給誰呢?
是提交給了我們的TxClient 模組。然後TxCliient 模組下有一個連線池,就是框架自定義的一個連線池(如圖DB 連線池);這個連線池其實就是在沒有通知事務之前一直佔有著這次事務的連線資源,就是沒有釋放。但是他在切面裡面執行了close 方法。在執行close的時候。
如果需要(TxManager)分散式事務框架的連線。他被叫做假關閉
,也就是沒有關閉,只是在執行了一次關閉方法。實際的資源是沒有釋放的。這個資源是掌握在LCN 的連線池裡的。
當TxManager 通知提交或事務回滾的時候呢?
TxManager 會通知我們的TxClient 端。然後TxClient 會去執行相應的提交或回滾。
提交或回滾之後再去關閉連線。這就是LCN 的事務協調機制。說白了就是代理DataSource 的機制;相當於是攔截了一下連線池,控制了連線池的事務提交。
特點:
TCC事務模式
TCC事務機制相對於傳統事務機制(X/Open XA Two-Phase-Commit),其特徵在於它不依賴資源管理器(RM)對XA的支援,而是通過對(由業務系統提供的)業務邏輯的排程來實現分散式事務。主要由三步操作,Try: 嘗試執行業務、 Confirm:確認執行業務、 Cancel: 取消執行業務。
如圖所示:
特點:
TCC方案分為Try、Confirm、Cancel三個階段,屬於補償性分散式事務。
Try:嘗試待執行的業務
這個過程並未執行業務,只是完成所有業務的一致性檢查,並預留好執行所需的全部資源;
Confirm:執行業務
這個過程真正開始執行業務,由於Try階段已經完成了一致性檢查,因此本過程直接執行,而不做任何檢查。並且在執行的過程中,會使用到Try階段預留的業務資源。
Cancel:取消執行的業務,如果任何一個服務的業務方法執行出錯,那麼這裡就需要進行補償。
這種TCC方案適用於一致性要求極高的系統中,比如金錢交易相關的系統中,不過可以看出,其基於補償的原理,因此,需要編寫大量的補償事務的程式碼,比較冗餘。不過現有開源的TCC框架,比如TCC-transaction。一般來說跟錢相關的,跟錢打交道的,支付、交易相關的場景,我們會用TCC,嚴格保證分散式事務要麼全部成功,要麼全部自動回滾,嚴格保證資金的正確性,保證在資金上不會出現問題。
本方案,乾脆不用原生的訊息表了,直接基於MQ 來實現事務。比如阿里的RocketMQ 就支援訊息事務。
流程如下:
上圖是早期RocketMQ的實現,依賴zookeeper,因為RocketMQ想追求AP模型,後期版本因為想更輕量化,將zookeeper去掉了。
1.系統 A 本地事務執行完之後,傳送個訊息到 MQ;
2.這裡會有個專門消費 MQ 的最大努力通知服務,這個服務會消費 MQ 然後寫入資料庫中記錄下來,或者是放入個記憶體佇列也可以,接著呼叫系統 B 的介面;
3.要是系統 B 執行成功就 ok 了;要是系統 B 執行失敗了,那麼最大努力通知服務就定時嘗試重新呼叫系統 B,反覆 N 次,最後還是不行就放棄。