數據齊舞:深入淺出分布式事務的八奇技
1. 引言
大家好,我是小?,一個漂泊江湖多年的 985 非科班程序員,曾混跡于國企、互聯(lián)網大廠和創(chuàng)業(yè)公司的后臺開發(fā)攻城獅。
今天,小?將帶大家探討分布式事務里的“八奇技”,幫助大家在實際的分布式系統(tǒng)中更好地運用事務。
2. 分布式事務常見的解決分案
分布式事務是在分布式系統(tǒng)中,跨越多個計算機節(jié)點或數據存儲系統(tǒng)進行的事務,在這種環(huán)境下保證事務的ACID(原子性、一致性、隔離性、持久性)屬性是一大挑戰(zhàn)。
對此,業(yè)界有以下 8 種常見的解決方案,俗稱 “八奇技”。
奇技1)2PC
二階段提交協(xié)議(Two-phase commit protocol),簡稱 2PC。兩階段提交是一種強一致性事務協(xié)議,它分為準備階段和提交階段。
圖片
在準備階段,協(xié)調者節(jié)點詢問所有參與者是否準備好提交事務,如果所有參與者都答應準備好了,那么在提交階段,協(xié)調者會通知所有參與者提交事務。
如果有任何一個參與者在準備階段沒有準備好,那么協(xié)調者會通知所有參與者回滾事務。
有熟悉 MySQL 的同學可能馬上想到了,MySQL 的事務提交就是通過幾種日志來實現二階段提交的。
不了解MySQL執(zhí)行流程的可以看我之前寫的這篇文章:一張圖看懂SQL執(zhí)行流程
優(yōu)點
- 原子性保證:2PC 協(xié)議可以保證所有參與者要么全部提交成功,要么全部失敗回滾,從而實現跨多個分布式節(jié)點的事務的原子性。
- 簡單直觀:2PC 的設計思路簡單,邏輯清晰,容易理解,這使得它在很多傳統(tǒng)的數據庫和分布式系統(tǒng)中得到了廣泛的應用,比如 MySQL 從 5.5 版本開始支持。
缺點
- 同步阻塞:在 2PC 的第一階段,所有參與者在響應協(xié)調者的準備請求后,必須等待最終的提交或回滾指令。這期間,所有參與者都處于阻塞狀態(tài),無法進行其他操作,導致資源鎖定時間較長,在高并發(fā)場景下很明顯不太適用。
- 單點故障:如果協(xié)調者在第二階段崩潰,參與者可能會無限期地等待指令,因為它們不知道應該提交還是回滾。這使得整個系統(tǒng)容易受到單點故障的影響。
- 數據不一致:如果在第二階段中協(xié)調者向某些參與者發(fā)送了提交指令,而其他參與者因為網絡問題沒有收到指令,那么這些沒有收到指令的參與者可能會選擇回滾,導致數據不一致。
- 復雜的恢復機制:當系統(tǒng)崩潰后,恢復過程非常復雜,所有參與者必須保持足夠的信息以便在系統(tǒng)恢復后能夠繼續(xù)完成 2PC 協(xié)議。
奇技2)3PC
三階段提交協(xié)議(Three-phase commit protocol),簡稱 3PC。三階段提交(3PC)是兩階段提交(2PC)的改進版本,它旨在減少在協(xié)調者和參與者之間的阻塞時間,同時增加系統(tǒng)在某些故障情況下的容錯能力,以下是 3PC 的三個階段:
- CanCommit 階段
協(xié)調者行動: 發(fā)送 CanCommit 請求到所有參與者,并等待回應。
參與者行動: 如果參與者可以提交事務,它就返回 Yes,并進入預備狀態(tài);如果不能提交,則返回 No。
- PreCommit 階段
協(xié)調者行動: 如果所有參與者回答 Yes,協(xié)調者發(fā)送 PreCommit 請求給所有參與者,并進入 Prepared 階段;如果有任何參與者回答 No,或者等待超時,協(xié)調者發(fā)送 abort 請求。
參與者行動: 在收到 PreCommit 請求后,參與者會執(zhí)行事務操作,寫入日志,但不提交,然后響應 ACK,并等待最終指令。如果參與者在這個階段超時沒有收到協(xié)調者的消息,它將中止事務。
DoCommit 階段
協(xié)調者行動: 一旦協(xié)調者收到所有參與者的 ACK,它會進入 DoCommit 階段,發(fā)送 commit 請求給所有參與者。
參與者行動: 參與者在收到 commit 請求后,提交事務,釋放所有事務鎖定的資源,并向協(xié)調者發(fā)送完成消息。
與 2PC 相比,3PC 在 PreCommit 階段引入了超時機制,允許參與者在沒有接收到協(xié)調者的最終指令時自行決定中止事務,這減少了協(xié)調者成為單點故障的可能性。
實際業(yè)務場景
3PC通常用于需要較高可靠性的分布式系統(tǒng)中,尤其是在那些不能接受長時間鎖定資源的場景。例如:
- 分布式數據庫系統(tǒng):分布式數據庫可能使用 3PC 來確保跨多個數據中心的事務一致性。例如,一個全球性的銀行可能需要在不同國家的分支機構之間處理賬戶轉賬,這時3PC可以減少在網絡延遲或某個分支機構失去響應時的影響。
- 電信網絡:在電信運營商的計費系統(tǒng)中,可能會使用 3PC 來同步跨多個服務點的賬單信息,這些系統(tǒng)通常要求高可用性和快速響應,因此不能長時間阻塞。
- 大型分布式系統(tǒng):對于需要跨多個服務和組件協(xié)調工作的大型分布式系統(tǒng),比如云計算平臺,3PC可以在保持事務一致性的同時,減少參與者等待協(xié)調者指令的時間。
使用 3PC 的考慮因素
雖然 3PC 提供了比 2PC 更好的容錯性和減少了阻塞的時間,但它仍然有一些缺點:
- 復雜性:3PC 比 2PC 更復雜,需要更多的消息交換和更多的狀態(tài)管理。
- 性能開銷:3PC 引入了額外的階段和網絡通信,可能會導致更大的性能開銷。
- 極端情況:即使是 3PC,在某些極端的網絡分區(qū)或多點故障情況下也可能無法保證事務的正確性。
因此,在實際應用中,需要權衡 3PC 帶來的好處與其復雜性和性能開銷之間的關系,確保它適合特定的業(yè)務場景和系統(tǒng)需求。
在某些情況下,其他的事務模型,如最大努力通知等最終一致性模型,可能會是更合適的選擇。
奇技3)TCC
TCC(Try-Confirm-Cancel)是一種應用層的分布式事務解決方案,它將事務分為三個步驟:嘗試(Try)、確認(Confirm)和取消(Cancel):
- 在 Try 階段,會預留必要的業(yè)務資源;
- 在 Confirm 階段,如果所有相關的業(yè)務操作都成功了,則正式執(zhí)行業(yè)務操作;
- 如果有操作失敗,則在 Cancel 階段執(zhí)行補償操作,回滾之前的預留資源。
圖片
假設我們買一張從深圳到北京的火車票,票價為 360 元,TCC 分為這三個步驟:
- Try:檢查錢包的錢是否大于等于 360,并鎖住資源(360 元和這張車票);
- Cancel:如果有一個資源鎖定失敗,則進行 cancel 釋放資源,這個過程中無論 cancel 還是其它操作失敗都進行重試 cancel,所以需要保證冪等性;
- Confirm:如果資源鎖定都成功,則進行 confirm,資源交換,這個過程中無論 confirm 還是其它操作失敗都進行重試 confirm,都需保證冪等性。
優(yōu)勢
TCC 的出現解決二階段提交的幾個缺點:
- 單點故障問題:引入了多個業(yè)務活動管理器,集群下高可用;
- 數據不一致問題:引入超時補償機制,由業(yè)務活動管理器來控制一致性;
- 同步阻塞問題:引入超時補償機制,不會鎖定同步,將資源轉換為業(yè)務邏輯形式,粒度更小。
奇技4)分布式補償事務(Saga)
Saga 是一種長事務的解決方案,它將一個大的分布式事務拆分成多個較小的本地事務,這些本地事務通過異步消息傳遞串聯(lián)起來。
每個本地事務執(zhí)行成功后,會發(fā)送消息觸發(fā)下一個事務的執(zhí)行。如果某個本地事務失敗,Saga 會執(zhí)行一系列補償操作(回滾之前的操作)來保持數據的一致性。
假設有一個旅游網站,用戶可以通過它預訂機票、酒店和租車服務。每個預訂步驟都可以視為一個 Saga 中的小事務:
- 用戶預訂機票。
- 用戶預訂酒店。
- 用戶預訂租車服務。
如果用戶成功完成了所有預訂步驟,那么整個旅行預訂就完成了。但如果在預訂租車服務時失敗了,那么 Saga 會開始執(zhí)行補償操作:
- 取消酒店預訂。
- 取消機票預訂。
通過這種方式,Saga 確保了用戶不會因為部分服務預訂失敗而損失金錢或留下未處理的預訂。
優(yōu)點
- 靈活性:Saga 允許每個小事務獨立管理,提高了系統(tǒng)的靈活性。
- 減少資源鎖定:由于 Saga 不需要在事務執(zhí)行過程中持續(xù)占用資源,因此可以減少長時間的資源鎖定,提高系統(tǒng)的并發(fā)能力。
- 容錯性:Saga 通過定義補償操作來處理失敗,增強了系統(tǒng)的容錯能力。
- 適用于微服務架構:在微服務架構中,Saga 可以跨服務邊界管理事務,每個服務獨立處理自己的事務和補償邏輯。
缺點
- 復雜性:實現 Saga 需要定義每個小事務的補償操作,這可能會增加系統(tǒng)的復雜性。
- 數據一致性:Saga 不能提供 2PC 那樣的即時一致性保證,它只能保證最終一致性,這在某些業(yè)務場景中可能是不夠的。
- 補償操作的難度:在某些情況下,補償操作可能很難實現,尤其是當事務有副作用時(比如發(fā)送了一個不可撤銷的通知)。
- 測試和調試:由于 Saga 涉及多個服務和補償邏輯,測試和調試可能會更加困難。
在選擇使用 Saga 模式時,需要仔細考慮業(yè)務場景是否適合最終一致性,以及是否能夠有效地實現和管理補償邏輯。對于那些需要高度一致性保證的場景,可能需要考慮其他事務管理機制。
奇技5)分布式鎖
在某些情況下,可以使用分布式鎖來確保多個分布式節(jié)點不會同時操作同一資源。這可以通過 Redis、ZooKeeper 等分布式協(xié)調服務來實現。
不知道分布式鎖或者不了解如何實現的,可以看這篇文章:說出來你可能不信,分布式鎖竟然這么簡單...
- 項目/公司: 使用 Redis、ZooKeeper 等實現分布式鎖的系統(tǒng)。
- 實用場景: 在電商秒殺活動中,防止超賣現象,確保同一時間只有一個請求能夠對庫存數量進行修改。
- 推薦場景: 當需要協(xié)調多個節(jié)點對共享資源進行訪問控制時,分布式鎖是一個有效的解決方案。
奇技6)本地消息表
圖片
本地消息表是一種確保分布式事務最終一致性的方法。它的工作原理是:
- 在執(zhí)行本地事務的同時,將需要異步執(zhí)行的遠程服務調用相關信息存儲在同一個本地數據庫的消息表中。
- 本地事務和消息表的寫入操作在同一個數據庫事務中完成,這樣可以保證要么都成功,要么都失敗,從而保證了數據的一致性。
- 本地事務提交后,一個獨立的消息發(fā)布程序會定期掃描消息表,對于未處理的消息,發(fā)布到消息隊列或直接調用遠程服務。
- 遠程服務處理完成后,消息會被標記為已處理,從而確保每條消息只被處理一次。
本地消息表是 ebay 公司提出的事務解決方案,它的核心原理是將需要分布式處理的任務通過消息日志的方式來異步執(zhí)行。消息日志可以存儲到本地文件、數據庫或消息隊列,再通過業(yè)務規(guī)則或人工發(fā)起重試。
本地消息表基于 BASE 理論,實現數據的最終一致性,實現過程中需要注意冪等性原則。
奇技7)可靠消息最終一致性
通過可靠消息服務保證消息的可靠傳輸,并在消息消費者那里進行本地事務處理,從而實現最終一致性,所以又被稱作消息事務。如果消息處理失敗,可以重試或者進行人工干預。
執(zhí)行流程:
- 發(fā)送 prepare 消息到消息中間件
- 發(fā)送成功后,執(zhí)行本地事務
如果事務執(zhí)行成功,則 commit,消息中間件將消息下發(fā)至消費端
如果事務執(zhí)行失敗,則回滾,消息中間件將這條 prepare 消息刪除
- 消費端接收到消息進行消費,如果消費失敗,則不斷重試
這種方案也是實現了「最終一致性」,和本地消息表類似,但是對比本地消息表實現方案,消息事務不需要再建消息表,而是將消息中間件的機制去做的,「不再依賴本地數據庫事務」了。
所以這種方案更適用于高并發(fā)的場景,目前市面上實現該方案的「只有阿里的 RocketMQ」。
奇技8)最大努力通知原則
最大努力通知也是一種基于消息的分布式事務解決方案,但它不保證 100% 的消息傳遞成功。它的工作原理是:
- 在本地事務執(zhí)行成功后,系統(tǒng)會嘗試通知其他的參與者或服務。
- 通知操作會盡最大努力去執(zhí)行,但如果失敗,系統(tǒng)不會無限重試。
- 該方案通常結合人工干預,例如,如果通知失敗,系統(tǒng)可能會記錄日志、發(fā)送報警、或者提供管理界面供操作人員手動處理。
本地消息表,或者通過 MQ 對事務進行通知都可以算作最大努力。
本地消息表通過后臺定時任務去異步保證數據的一致性,就是一種最大努力通知的思想:代表系統(tǒng)各模塊之間已經最大程度地保證事務的最終一致性了。
3. 小結
在選擇分布式事務解決方案時,需要根據業(yè)務需求、系統(tǒng)復雜度、性能要求等因素進行權衡。
例如,對于業(yè)務場景要求數據的一致性非常高,且可以接受一定程度的性能損失時,2PC 或者 3PC 是很好的選擇。
對于復雜業(yè)務流程中的分布式事務,需要在業(yè)務層進行更細粒度控制時,TCC 是一個好的選擇。比如,用戶在電商平臺下單購買商品,涉及到庫存、賬戶余額、積分等多個服務的數據變更。
而對于可容忍短時間內數據不一致的業(yè)務,則可以考慮最終一致性相關的解決方案,如:本地消息表、消息事務及最大努力通知方案等等。
因此,當我們探討分布式事務時,不僅要把握好用戶痛點和實際需求,還要結合每個分布式事務解決方案的特點,才能把 “八奇技” 用到出神入化之境。