面試官:億級流量架構分布式事務如何實作?我懵了,,
作者:等不到的口琴
來源:https://www.cnblogs.com/Courage129/p/14433462.html
分布式事務以及分布式鎖是分布式中難點,分布式事務一篇文章可能寫不完,我的習慣時從基本概念出發,一步一步開始介紹,前面會先梳理事務中一些基本概念,對基本概念十分清楚的話可以直接看"一致性討論"以及后面的部分,予己方便總結回顧、與他交流分享,
什么是分布式事務
在日常生活中,很多事要么全部做,要么全部不做,不能只做一部分,不然就會產生其他復雜的問題,很多人喜歡舉轉賬的例子,對于同一個賬號,A在湖北往出轉500,B在廣東取錢500,那么A轉出去之后要將A賬號的錢數目扣除,B賬號數目增加: 事務 = (A賬號扣除500,B賬號增加500)
看到沒,像這樣多個步驟放在一起,就是事務,要么都執行,要么都不執行,如果我們的資料存盤在多個資料庫中,也就是存在跨庫呼叫,由于網路具有不安全性以及延時性,如何保證事務分布式執行呢?如果執行到一半斷電又該如何處理?在講解分布式事務之前先簡單回顧事務的一些特點,俗稱 ACID ,下面逐一講解:
原子性(Atomic)
在化學中,分子構成的物質,分子是保持化學特性的最小單位,如 H2O,CO2H2O,CO2 等,由原子構成的物質,原子保持物質特性,像 FeFe 啥的,意思就是不可分割,再分成質子中子啥的就不是我們認為的物質了,這兒的原子性也是這個道理,就是事務不可以再拆分,例如上面的事務,看著可以是由兩個程序組成的事務,但是你拆開就不是我們認為該有的程序,所以,事務不可再分,具有原子性,
一致性(Consistency)
一致性也很好理解,對于上面的兩個賬戶,如果銀行想知道自己這兒被存了多少錢,那么這個事務執行前,A賬號有500塊,B賬號沒有錢,銀行賬戶總共500塊,事務執行后A賬號沒有錢,B賬號有500塊,也就是這個500塊是一定的,不可能出現A賬號有500塊,B賬號也有500塊, 那就資料不一致了,這樣的話,說明事務中某些步驟執行出現了問題,產生中間資料,那么就不一致,
在分布式中,對于一個結果,多處同時查詢,得出的結果應該是一致的,
隔離性(Isolation)
一個事務在未完成時,另一個事務不會影響到它,也就是如果B還給C轉賬1000,記為事務2:
事務1 = (A賬號扣除500,B賬號增加500)
事務2 = (B賬號扣除1000,C賬號增加1000)
這兩個事務之間不會產生影響,也就是不會發生A轉出的500塊到達C賬號這種情況,
持久性(Durability)
持久化,一般是意味著將資料寫入磁盤,不會輕易改變的意思,這兒是事務提交之后,會影響到資料庫,不會丟失,這也就意味著,隨著系統越來越龐大,我們為了提高可用性、維護性、吞吐量等等技術指標,就算改善原有架構,業務計算的問題解決后,資料庫還是會成為整個系統中的瓶頸,
一致性的討論
ACID本質而言都是為了保護資料的一致性,而資料資料持久化時會觸發資料庫操作,造成效率低小,所以圍繞一致性(效率)產生了一些討論,分別是強一致性、弱一致性、最終一致性,
強一致性
任何一次讀都能讀到某個資料的最近一次寫的資料,系統中的所有行程,看到的操作順序,都和全域時鐘下的順序一致,簡言之,在任意時刻,所有節點中的資料是一樣的,這就要求資料一有改變就寫到資料庫,
弱一致性
資料更新后,不要求及時寫會資料庫以及同步到所有節點,也就是這時候資料與真實資料可能有一些出入,對于架構而言,如果能容忍后續的訪問只能訪問到部分或者全部訪問不到,則是弱一致性,
最終一致性
不保證在任意時刻任意節點上的同一份資料都是相同的,也就是有些節點資料可能是準確的,有的可能是不準確的, 但是隨著時間的遷移,不同節點上的同一份資料總是在向趨同的方向變化,簡單說,就是在一段時間后,節點間的資料會最終達到一致狀態,
三種一致性中,強一致性資料更加可靠,但是由于時時刻刻要求所有資料庫保持資料一致,所以效率低下,資料沒有統一完,請求就沒法得到回應,高并發場景下,體驗不太好,所以在實際使用中,根據不同的業務選擇是一致性也不同,購物時賬號付錢肯定是強一致性,但是商品庫存資料就不一定非要強一致性,至于商品下面的評論啥的,甚至可以選擇弱一致性,
分庫分表
前面講過集群的AKF拆分原則( Redis集群拆分原則之AKF ),大概意思是硬體性能是由上限的,當硬體沒法支撐請求流量時,可以將流量分發到不同的服務器上,AKF拆分之Y軸、Z軸拆分是業務拆分與資料拆分,那也就會涉及到將資料庫中的資料拆分存盤在不同的地方,這就叫分庫分表,不同型別資料存盤在不同資料庫中做多機存盤和負載,這樣一來,傳統的事務機制ACID便無法正常運行,
分庫分表內容是資料切分(Sharding),以及切分后對資料的定位、整合,具體來說, 資料切分就是將資料分散存盤到多個資料庫中,使得單一資料庫中的資料量變小,通過擴充主機的數量緩解單一資料庫性能問題,從而達到提升資料庫操作性能的目的,
資料切分根據其切分型別,可以分為兩種方式:垂直(縱向)切分和水平(橫向)切分,
垂直拆分
垂直切分常見有垂直分庫和垂直分表兩種,兩種含義類似,
垂直分庫就是根據業務耦合性,將關聯度低的不同表存盤在不同的資料庫,做法與大系統拆分為多個小系統類似,按業務分類進行獨立劃分,與"微服務治理"的做法相似,每個微服務使用單獨的一個資料庫,如圖:

垂直分表類似,例如將一張表包含一個人所有資訊,例如姓名、身份證、性別、身高、體重、省、市、區、村、專業、G點等等,那么可以拆分成三個表:
第一個表只包含基本資訊(姓名、身份證、性別、身高、體重);
第二個表包含籍貫資訊(省、市、區、村);
第三個表包含學習資訊(專業、G點),
垂直拆分優缺點
垂直切分的優點:
- 解決業務系統層面的耦合,業務清晰
- 與微服務的治理類似,也能對不同業務的資料進行分級管理、維護、監控、擴展等
- 高并發場景下,垂直切分一定程度的提升IO、資料庫連接數、單機硬體資源的瓶頸
垂直切分的缺點:
- 部分表無法join,只能通過介面聚合方式解決,提升了開發的復雜度
- 分布式事務處理復雜
- 依然存在單表資料量過大的問題(需要水平切分)
水平拆分
上面對資料庫垂直拆分之后,如果某個庫還是好大,比如存盤的資料極其龐大,那么可以再對資料庫進行水平的拆分:

上面的水平拆分時按照ID區間來切分,例如:將userId為110000的記錄分到第一個庫,1000120000的分到第二個庫,以此類推,某種意義上,某些系統中使用的"冷熱資料分離",將一些使用較少的歷史資料遷移到其他庫中,業務功能上只提供熱點資料的查詢,也是類似的實踐,
除了上面按照用戶ID區間拆分,也可以做Hash運算拆分,這兒就不詳細展開了,
水平拆分優缺點
水平拆分優點在于:
- 單表大小可控
- 天然便于水平擴展,后期如果想對整個分片集群擴容時,只需要添加節點即可,無需對其他分片的資料進行遷移
- 使用分片欄位進行范圍查找時,連續分片可快速定位分片進行快速查詢,有效避免跨分片查詢的問題,
水平拆分缺點:
- 熱點資料成為性能瓶頸,連續分片可能存在資料熱點,例如按時間欄位分片,有些分片存盤最近時間段內的資料,可能會被頻繁的讀寫,而有些分片存盤的歷史資料,則很少被查詢
分庫分表帶來的問題
分庫分表能有效的緩解單機和單庫帶來的性能瓶頸和壓力,突破網路IO、硬體資源、連接數的瓶頸,同時也帶來了一些問題,前面說過,事務包含一組子操作,這些造作要么全部執行,要么全部不執行,但是分庫之后,一個事務可能涉及多個資料庫或者多個表擴庫執行,而網路具有不穩定性,也就是事務執行難度加大,分表分庫后事務為了與傳統事務做出區別,叫做分布式事務(跨分片事務),
跨分片事務也是分布式事務,沒有簡單的方案,一般可使用"XA協議"和"兩階段提交"處理,
分布式事務能最大限度保證了資料庫操作的原子性,但在提交事務時需要協調多個節點,推后了提交事務的時間點,延長了事務的執行時間,導致事務在訪問共享資源時發生沖突或死鎖的概率增高,隨著資料庫節點的增多,這種趨勢會越來越嚴重,從而成為系統在資料庫層面上水平擴展的枷鎖,
最終一致性
對于那些性能要求很高,但對一致性要求不高的系統,往往不苛求系統的實時一致性,只要在允許的時間段內達到最終一致性即可,可采用事務補償的方式,與事務在執行中發生錯誤后立即回滾的方式不同,事務補償是一種事后檢查補救的措施,一些常見的實作方法有:對資料進行對賬檢查,基于日志進行對比,定期同標準資料來源進行同步等等,事務補償還要結合業務系統來考慮,
分布式事務解決思路
講這個之前需要先簡單回顧CAP原則和Base理論,因為分布式事務不同于 ACID 的剛性事務,在分布式場景下基于 BASE 理論,提出了柔性事務的概念,要想通過柔性事務來達到最終的一致性,就需要依賴于一些特性,這些特性在具體的方案中不一定都要滿足,因為不同的方案要求不一樣;但是都不滿足的話,是不可能做柔性事務的,
CAP原則
CAP一般人可能聽了不下一百遍了,很多人都說CAP是"三選二"的關系,讓人誤以為有AC這種情況,但是實際CAP是二選一的關系,這個在2012年已經有一篇論文進行解釋: CAP Twelve Years Later: How the "Rules" Have Changed
相當于是對之前三選二說法進行修正,CAP中P(磁區容錯性)是必須具備的,在滿足P的前提下,很難同時滿足A(可用性)和C(一致性),但是在之后,又有一篇文章: Harvest, yield, and scalable tolerant systems ,這篇論文是基于上面那篇“CAP 12年后”的論文寫的,它主要提出了 Harvest 和 Yield 概念,并把上面那篇論文中所討論的東西講得更為仔細了一些,簡單來說就是滿足P之后,C和A在放寬約束后可以得到兼顧,并不是非此即彼的關系,說遠了,
為什么P是必須的?
為什么CAP原則中磁區容錯性是必須的呢,首先要理解什么是磁區容錯性,磁區,這兒說的是網路,網路集群設計到很多的服務器,某一瞬間網路不穩定,那么相當于將網路分成了不同的區,假設分成了兩個區,這時候如果有一筆交易:
對磁區一發出訊息:A給B轉賬100元,對磁區二發出訊息:A給B轉賬200元
那么對于兩個磁區而言,有兩種情況:
a)無可用性,即這兩筆交易至少會有一筆交易不會被接受;
b)無一致性,一半看到的是 A給B轉賬100元而另一半則看到 A給B轉賬200元,
所以,磁區容忍性必須要滿足,解決策略是一個資料項復制到多個節點上,那么出現磁區之后,這一資料項就可能分布到各個區里,容忍性就提高了,
Base理論
在很多時候,我們并不需要強一致性的系統,所以后來,人們爭論關于資料一致性和可用性時,主要是集中在強一致性的 ACID 或最終一致性的 BASE中, BASE是對CAP中一致性和可用性權衡的結果,其來源于對大規模互聯網分布式系統實踐的總結,是基于CAP定律逐步演化而來,其核心思想是即使無法做到強一致性,但每個應用都可以根據自身業務特點,才用適當的方式來使系統打到最終一致性,
BASE理論是Basically Available(基本可用),Soft State(軟狀態)和Eventually Consistent(最終一致性)三個短語的縮寫,
基本可用
假設系統,出現了不可預知的故障,但還是能用,相比較正常的系統而言:
- 回應時間上的損失 :正常情況下的搜索引擎0.5秒即回傳給用戶結果,而基本可用的搜索引擎可以在2秒作用回傳結果,
- 功能上的損失 :在一個電商網站上,正常情況下,用戶可以順利完成每一筆訂單,但是到了大促期間,為了保護購物系統的穩定性,部分消費者可能會被引導到一個降級頁面,
這就叫基本可用
軟狀態
相對于原子性而言,要求多個節點的資料副本都是一致的,這是一種“硬狀態”,軟狀態指的是:允許系統中的資料存在中間狀態,并認為該狀態不影響系統的整體可用性,即允許系統在多個不同節點的資料副本存在資料延時,
最終一致性
上面說軟狀態,然后不可能一直是軟狀態,必須有個時間期限,在期限過后,應當保證所有副本保持資料一致性,從而達到資料的最終一致性,這個時間期限取決于網路延時、系統負載、資料復制方案設計等等因素,
Base其核心思想是:
既然無法做到強一致性(Strong consistency),但每個應用都可以根據自身的業務特點,采用適當的方式來使系統達到最終一致性(Eventual consistency),有了Base理論就可以開始講述分布式事務的處理思路了,
二階段提交協議
二階段提交(2PC:Two-Phase Commit),顧名思義,該協議將一個分布式的事務程序拆分成兩個階段: 投票 和 事務提交 ,為了讓整個資料庫集群能夠正常的運行,該協議指定了一個 協調者 單點,用于協調整個資料庫集群各節點的運行,為了簡化描述,我們將資料庫集群中的各個節點稱為 參與者 ,三階段提交協議中同樣包含協調者和參與者這兩個角色定義,后面再說,
第一階段:投票
該階段的主要目的在于打探資料庫集群中的各個參與者是否能夠正常的執行事務,具體步驟如下:
- 協調者向所有的參與者發送事務執行請求,并等待參與者反饋事務執行結果;
- 事務參與者收到請求之后,執行事務但不提交,并記錄事務日志;
- 參與者將自己事務執行情況反饋給協調者,同時阻塞等待協調者的后續指令,
第二階段:事務提交
在經過第一階段協調者的詢盤之后,各個參與者會回復自己事務的執行情況,這時候存在 3 種可能性:
- 所有的參與者都回復能夠正常執行事務,
- 一個或多個參與者回復事務執行失敗,
- 協調者等待超時,
對于第 1 種情況,協調者將向所有的參與者發出提交事務的通知,具體步驟如下:
- 協調者向各個參與者發送 commit 通知,請求提交事務;
- 參與者收到事務提交通知之后執行 commit 操作,然后釋放占有的資源;
- 參與者向協調者回傳事務 commit 結果資訊,

對于第 2 和第 3 種情況,協調者均認為參與者無法成功執行事務,為了整個集群資料的一致性,所以要向各個參與者發送事務回滾通知,具體步驟如下:
- 協調者向各個參與者發送事務 rollback 通知,請求回滾事務;
- 參與者收到事務回滾通知之后執行 rollback 操作,然后釋放占有的資源;
- 參與者向協調者回傳事務 rollback 結果資訊,

兩階段提交協議解決的是分布式資料庫資料強一致性問題,實際應用中更多的是用來解決事務操作的原子性,下圖描繪了協調者與參與者的狀態轉換,

站在協調者的角度,在發起投票之后就進入了 WAIT 等待狀態,等待所有參與者回復各自事務執行狀態,并在收到所有參與者的回復后決策下一步是發送 commit提交 或 rollback回滾資訊,
站在參與者的角度,當回復完協調者的投票請求之后便進入 READY 狀態(能夠正常執行事務),接下去就是等待協調者最終的決策通知,一旦收到通知便可依據決策執行 commit 或 rollback 操作,
兩階段提交協議原理簡單、易于實作,但是缺點也是顯而易見的,包含如下:
- 單點問題
協調者在整個兩階段提交程序中扮演著舉足輕重的作用,一旦協調者所在服務器宕機,就會影響整個資料庫集群的正常運行,比如在第二階段中,如果協調者因為故障不能正常發送事務提交或回滾通知,那么參與者們將一直處于阻塞狀態,整個資料庫集群將無法提供服務,
- 同步阻塞
兩階段提交執行程序中,所有的參與者都需要聽從協調者的統一調度,期間處于阻塞狀態而不能從事其他操作,這樣效率極其低下,
- 資料不一致性
兩階段提交協議雖然是分布式資料強一致性所設計,但仍然存在資料不一致性的可能性,比如在第二階段中,假設協調者發出了事務 commit 通知,但是因為網路問題該通知僅被一部分參與者所收到并執行了commit 操作,其余的參與者則因為沒有收到通知一直處于阻塞狀態,這時候就產生了資料的不一致性,
針對上述問題可以引入 超時機制 和 互詢機制在很大程度上予以解決,
超時機制
對于協調者來說如果在指定時間內沒有收到所有參與者的應答,則可以自動退出 WAIT 狀態,并向所有參與者發送 rollback 通知,對于參與者來說如果位于 READY 狀態,但是在指定時間內沒有收到協調者的第二階段通知,則不能武斷地執行 rollback 操作,因為協調者可能發送的是 commit 通知,這個時候執行 rollback 就會導致資料不一致,
互詢機制
此時,我們可以介入互詢機制,讓參與者 A 去詢問其他參與者 B 的執行情況,如果 B 執行了 rollback 或 commit 操作,則 A 可以大膽的與 B 執行相同的操作;如果 B 此時還沒有到達 READY 狀態,則可以推斷出協調者發出的肯定是 rollback 通知;如果 B 同樣位于 READY 狀態,則 A 可以繼續詢問另外的參與者,只有當所有的參與者都位于 READY 狀態時,此時兩階段提交協議無法處理,將陷入長時間的阻塞狀態,
三階段提交協議
三階段提交協議(3PC:Three-Phase Commit), 針對兩階段提交存在的問題,三階段提交協議通過引入一個 預詢盤 階段,以及超時策略來減少整個集群的阻塞時間,提升系統性能,三階段提交的三個階段分別為:預詢盤(can_commit)、預提交(pre_commit),以及事務提交(do_commit),

第一階段:預詢盤
該階段協調者會去詢問各個參與者是否能夠正常執行事務,參與者根據自身情況回復一個預估值,相對于真正的執行事務,這個程序是輕量的,具體步驟如下:
- 協調者向各個參與者發送事務詢問通知,詢問是否可以執行事務操作,并等待回復;
- 各個參與者依據自身狀況回復一個預估值,如果預估自己能夠正常執行事務就回傳確定資訊,并進入預備狀態,否則回傳否定資訊,
第二階段:預提交
本階段協調者會根據第一階段的詢盤結果采取相應操作,詢盤結果主要有 3 種:
- 所有的參與者都回傳確定資訊,
- 一個或多個參與者回傳否定資訊,
- 協調者等待超時,
針對第 1 種情況,協調者會向所有參與者發送事務執行請求,具體步驟如下:
- 協調者向所有的事務參與者發送事務執行通知;
- 參與者收到通知后執行事務但不提交;
- 參與者將事務執行情況回傳給客戶端,
在上述步驟中,如果參與者等待超時,則會中斷事務,針對第 2 和第 3 種情況,協調者認為事務無法正常執行,于是向各個參與者發出 abort 通知,請求退出預備狀態,具體步驟如下:
- 協調者向所有事務參與者發送 abort 通知;
- 參與者收到通知后中斷事務,

第三階段:事務提交
如果第二階段事務未中斷,那么本階段協調者將會依據事務執行回傳的結果來決定提交或回滾事務,分為 3 種情況:
- 所有的參與者都能正常執行事務,
- 一個或多個參與者執行事務失敗,
- 協調者等待超時,
針對第 1 種情況,協調者向各個參與者發起事務提交請求,具體步驟如下:
- 協調者向所有參與者發送事務 commit 通知;
- 所有參與者在收到通知之后執行 commit 操作,并釋放占有的資源;
- 參與者向協調者反饋事務提交結果,

針對第 2 和第 3 種情況,協調者認為事務無法成功執行,于是向各個參與者發送事務回滾請求,具體步驟如下:
- 協調者向所有參與者發送事務 rollback 通知;
- 所有參與者在收到通知之后執行 rollback 操作,并釋放占有的資源;
- 參與者向協調者反饋事務回滾結果,

在本階段如果因為協調者或網路問題,導致參與者遲遲不能收到來自協調者的 commit 或 rollback 請求,那么參與者將不會如兩階段提交中那樣陷入阻塞,而是等待超時后繼續 commit,相對于兩階段提交雖然降低了同步阻塞,但仍然無法完全避免資料的不一致,兩階段提交協議中所存在的長時間阻塞狀態發生的幾率還是非常低的,所以雖然三階段提交協議相對于兩階段提交協議對于資料強一致性更有保障,但是因為效率問題,兩階段提交協議在實際系統中反而更加受寵,
TCC模式
TCC是Try、Confirm 和 Cancel三個單詞首字母縮寫,它們分別的職責是:
Try:負責預留資源(比如新建一條狀態=PENDING的訂單);
做業務檢查,簡單來說就是不能預留已經被占用的資源;
隔離預留資源,
Confirm:負責落地所預留的資源
真正的執行業務使用try階段預留的資源,冪等,
Cancel:負責撤銷所預留的資源
需要用戶根據自己的業務場景實作 Try、Confirm 和 Cancel 三個操作;事務發起方在一階段執行 Try 方式,在二階段提交執行 Confirm 方法,二階段回滾執行 Cancel 方法,
關于預留資源要多說兩句,資源都是有限的,因此預留資源都是有時效的,如果當預留資源遲遲得不到Confirm——我們將這種情況稱為timeout——參與方會自行將其Cancel,也就是說參與方對于資源具有自我管理能力,這樣可以避免因發起方的問題導致資源被長期占用,
TCC增加了業務檢查和撤銷事務的功能,同時,TCC將2PC資料庫層面的動作提升到了服務層面,不同的是TCC的所有動作都是一個本地事務,每個本地事務都在動作完成后commit到資料庫:
- Try相當于2PC的Commit request phase,外加了業務檢查邏輯
- Confirm相當于2PC的Commit phase的commit動作
- Cancel相當于2PC的Commit phase的rollback動作
流程步驟:
- 發起方 發送Try到所有 參與方
- 每個 參與方 執行Try,預留資源
- 發起方 收到所有 參與方 的Try結果
- 發起方 發送Confirm/Cancel到所有 參與房
- 每個 參與方 執行Confirm/Cancel
- 發起方 收到所有 參與方 的Confirm/Cancel結果
流程和兩階段提交非常類似,
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2021最新版)
2.別在再滿屏的 if/ else 了,試試策略模式,真香!!
3.臥槽!Java 中的 xx ≠ null 是什么新語法?
4.Spring Boot 2.5 重磅發布,黑暗模式太炸了!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/346847.html
標籤:Java
