我們一起聊聊分布式事務
一. 分布式事務問題的理論模型
1.1 CAP三進二
CAP的定義
- Consistency (一致性):
“all nodes see the same data at the same time”,即更新操作成功并返回客戶端后,所有節(jié)點在同一時間的數據完全一致,這就是分布式的一致性。一致性的問題在并發(fā)系統(tǒng)中不可避免,對于客戶端來說,一致性指的是并發(fā)訪問時更新過的數據如何獲取的問題。從服務端來看,則是更新如何復制分布到整個系統(tǒng),以保證數據最終一致。
- Availability (可用性):
可用性指“Reads and writes always succeed”,即服務一直可用,而且是正常響應時間。好的可用性主要是指系統(tǒng)能夠很好的為用戶服務,不出現用戶操作失敗或者訪問超時等用戶體驗不好的情況。
- Partition Tolerance (分區(qū)容錯性):
即分布式系統(tǒng)在遇到某節(jié)點或網絡分區(qū)故障的時候,仍然能夠對外提供滿足一致性或可用性的服務。比如現在的分布式系統(tǒng)中有某一個或者幾個機器宕掉了,其他剩下的機器還能夠正常運轉滿足系統(tǒng)需求,對于用戶而言并沒有什么體驗上的影響。
CAP三進二的含義
CAP理論就是說在分布式存儲系統(tǒng)中,最多只能實現上面的兩點。而由于當前的網絡硬件肯定會出現延遲丟包等問題,所以分區(qū)容忍性是我們必須需要實現的。所以我們只能在一致性和可用性之間進行權衡,沒有NoSQL系統(tǒng)能同時保證這三點。
CA:傳統(tǒng)Oracle數據庫 || AP:大多數網站架構的選擇 || CP:Redis、Mongodb
注意:分布式架構的時候必須做出取舍。一致性和可用性之間取一個平衡。多余大多數web應用,其實并不需要強一致性。因此犧牲C換取P,這是目前分布式數據庫產品的方向。
CAP的核心理論是:就是一個分布式體統(tǒng)不可能同時滿足一致性、可用性、分區(qū)容錯性三個需求,最多只能較好的同時滿足兩個。
- CA without P:放棄P的同時也就意味著放棄了系統(tǒng)的擴展性,也就是分布式節(jié)點受限,沒辦法部署子節(jié)點,這是違背分布式系統(tǒng)設計的初衷的。
- CP without A:如果不要求A(可用),相當于每個請求都需要在服務器之間保持強一致,而P(分區(qū))會導致同步時間無限延長(也就是等待數據同步完才能正常訪問服務),一旦發(fā)生網絡故障或者消息丟失等情況,就要犧牲用戶的體驗,等待所有數據全部一致了之后再讓用戶訪問系統(tǒng)。設計成CP的系統(tǒng)其實不少,最典型的就是分布式數據庫,如Redis、HBase等。對于這些分布式數據庫來說,數據的一致性是最基本的要求,因為如果連這個標準都達不到,那么直接采用關系型數據庫就好,沒必要再浪費資源來部署分布式數據庫。
- AP wihtout C:高可用并允許分區(qū),放棄一致性。一旦分區(qū)發(fā)生,節(jié)點之間可能會失去聯(lián)系,為了高可用,每個節(jié)點只能用本地數據提供服務,而這樣會導致全局數據的不一致性。典型的應用就如某米的搶購手機場景,可能前幾秒你瀏覽商品的時候頁面提示是有庫存的,當你選擇完商品準備下單的時候,系統(tǒng)提示你下單失敗,商品已售完。這其實就是先在 A(可用性)方面保證系統(tǒng)可以正常的服務,然后在數據的一致性方面做了些犧牲,雖然多少會影響一些用戶體驗,但也不至于造成用戶購物流程的嚴重阻塞。
1.2 BASE定理
在上邊,我們談到,因為P總是存在的,放棄不了。另外,可用性、一致性也是我們一般系統(tǒng)必須要滿足的,如何在可用性和一致性進行權衡,所以就出現了各種一致性的理論與算法。
BASE理論是:BASE是指基本可用(Basically Available)、軟狀態(tài)( Soft State)、最終一致性( Eventual Consistency)。BASE是對CAP中一致性和可用性權衡的結果,其來源于對大規(guī)模互聯(lián)網系統(tǒng)分布式實踐的總結,是基于CAP定理逐步演化而來的,其核心思想是即使無法做到強一致性(Strong consistency),但每個應用都可以根據自身的業(yè)務特點,采用適當的方式來使系統(tǒng)達到最終一致性(Eventual consistency)。
- 基本可用:
基本可用是指分布式系統(tǒng)在出現不可預知故障的時候,允許損失部分可用性,以下兩個就是“基本可用”的典型例子。
1. 響應時間上的損失:正常情況下,一個在線搜索引擎需要在0.5秒之內返回給用戶相應的查詢結果,但由于出現故障(比如系統(tǒng)部分機房發(fā)生斷電或斷網故障),查詢結果的響應時間增加到了1~2秒。
2. 功能上的損失:正常情況下,在一個電子商務網站上進行購物,消費者幾乎能夠順利地完成每一筆訂單,但是在一些節(jié)日大促購物高峰的時候,由于消費者的購物行為激增,為了保護購物系統(tǒng)的穩(wěn)定性,部分消費者可能會被引導到一個降級頁面。
- 弱狀態(tài)
弱狀態(tài)也稱為軟狀態(tài),和硬狀態(tài)相對,是指允許系統(tǒng)中的數據存在中間狀態(tài),并認為該中間狀態(tài)的存在不會影響系統(tǒng)的整體可用性,即允許系統(tǒng)在不同節(jié)點的數據副本之間進行數據同步的過程存在延時。
- 最終一致性
最終一致性強調的是系統(tǒng)中所有的數據副本,在經過一段時間的同步后,最終能夠達到一個一致的狀態(tài)。因此,最終一致性的本質是需要系統(tǒng)保證最終數據能夠達到一致,而不需要實時保證系統(tǒng)數據的強一致性。
1.3 二階段提交協(xié)議
二階段提交(Two-phase Commit)被稱為是一種協(xié)議(Protocol)。在分布式系統(tǒng)中,每個節(jié)點雖然可以知曉自己的操作時成功或者失敗,卻無法知道其他節(jié)點的操作的成功或失敗。當一個事務跨越多個節(jié)點時,為了保持事務的ACID特性,需要引入一個作為協(xié)調者的組件來統(tǒng)一掌控所有節(jié)點(稱作參與者)的操作結果并最終指示這些節(jié)點是否要把操作結果進行真正的提交(比如將更新后的數據寫入磁盤等等)。
二階段提交分為兩階段:
二階段提交優(yōu)點:
- 盡量保證了數據的強一致,但不是100%一致。
缺點:
- 單點故障,由于協(xié)調者的重要性,一旦協(xié)調者發(fā)生故障,參與者會一直阻塞,尤其時在第二階段,協(xié)調者發(fā)生故障,那么所有的參與者都處于鎖定事務資源的狀態(tài)中,而無法繼續(xù)完成事務操作
- 同步阻塞,由于所有節(jié)點在執(zhí)行操作時都是同步阻塞的,當參與者占有公共資源時,其他第三方節(jié)點訪問公共資源不得不處于阻塞狀態(tài)
- 數據不一致,在第二階段中,當協(xié)調者向參與者發(fā)送提交事務請求之后,發(fā)生了局部網絡異常或者在發(fā)送提交事務請求過程中協(xié)調者發(fā)生了故障,這會導致只有一部分參與者接收到了提交事務請求。這部分參與者接到提交事務請求之后就會執(zhí)行提交事務操作。但是其他部分未接收到提交事務請求的參與者則無法提交事務。從而導致分布式系統(tǒng)中的數據不一致
1.4 三階段提交協(xié)議
三階段提交(Three-phase commit),三階段提交是為解決兩階段提交協(xié)議的缺點而設計的。與兩階段提交不同的是,三階段提交是“非阻塞”協(xié)議。三階段提交在兩階段提交的第一階段與第二階段之間插入了一個準備階段,使得原先在兩階段提交中,參與者在投票之后,由于協(xié)調者發(fā)生崩潰或錯誤,而導致參與者處于無法知曉是否提交或者中止的“不確定狀態(tài)”所產生的可能相當長的延時的問題得以解決。
三階段提交的三個階段:
詢問階段 CanCommit
協(xié)調者向參與者發(fā)送CanCommit請求,參與者如果可以提交就返回Yes響應,否則返回No響應。
準備階段 PreCommit
協(xié)調者根據參與者在詢問階段的響應判斷是否執(zhí)行事務還是中斷事務:
- 如果所有參與者都返回Yes,則執(zhí)行事務
- 如果參與者有一個或多個參與者返回No或者超時,則中斷事務
參與者執(zhí)行完操作之后返回ACK響應,同時開始等待最終指令
提交階段 DoCommit
協(xié)調者根據參與者在準備階段的響應判斷是否執(zhí)行事務還是中斷事務:
- 如果所有參與者都返回正確的ACK響應,則提交事務
- 如果參與者有一個或多個參與者收到錯誤的ACK響應或者超時,則中斷事務
- 如果參與者無法及時接收到來自協(xié)調者的提交或者中斷事務請求時,會在等待超時之后,會繼續(xù)進行事務提交
協(xié)調者收到所有參與者的ACK響應,完成事務。
解決二階段提交時的問題
相比二階段提交,三階段提交降低了阻塞范圍,在等待超時后協(xié)調者或參與者會中斷事務。避免了協(xié)調者單點問題。階段 3 中協(xié)調者出現問題時,參與者會繼續(xù)提交事務。
三階段提交的問題
數據不一致問題依然存在,當在參與者收到 preCommit 請求后等待 do commite 指令時,此時如果協(xié)調者請求中斷事務,而協(xié)調者無法與參與者正常通信,會導致參與者繼續(xù)提交事務,造成數據不一致。
所以無論是2PC還是3PC都不能保證分布式系統(tǒng)中的數據100%一致。
二. 解決方式
2.1 補償事務(TCC)
TCC 是基于二階段提交協(xié)議的服務化編程模型,是把一個完整的業(yè)務拆分為三個步驟
TCC分為三個階段:
1. Try 階段是做業(yè)務檢查(一致性)及資源預留(隔離),此階段僅是一個初步操作,它和后續(xù)的Confirm 一起才能
真正構成一個完整的業(yè)務邏輯。
2. Confirm 階段是做確認提交,Try階段所有分支事務執(zhí)行成功后開始執(zhí)行 Confirm。通常情況下,采用TCC則
認為 Confirm階段是不會出錯的。即:只要Try成功,Confirm一定成功。若Confirm階段真的出錯了,需引
入重試機制或人工處理。
3. Cancel 階段是在業(yè)務執(zhí)行錯誤需要回滾的狀態(tài)下執(zhí)行分支事務的業(yè)務取消,預留資源釋放。通常情況下,采
用TCC則認為Cancel階段也是一定成功的。若Cancel階段真的出錯了,需引入重試機制或人工處理。
優(yōu)點:
性能提升:具體業(yè)務來實現控制資源鎖的粒度變小,不會鎖定整個資源。
數據最終一致性:基于 Confirm 和 Cancel 的冪等性,保證事務最終完成確認或者取消,保證數據的一致性。
可靠性:解決了 XA 協(xié)議的協(xié)調者單點故障問題,由主業(yè)務方發(fā)起并控制整個業(yè)務活動,業(yè)務活動管理器也變成多點,引入集群。
缺點:TCC 的 Try、Confirm 和 Cancel 操作功能要按具體業(yè)務來實現,業(yè)務耦合度較高,提高了開發(fā)成本。
舉例說明:
2.2 本地消息表
本地消息表的核心思想是將分布式事務拆分成本地事務進行處理。
在訂單系統(tǒng)新增一條消息表,將新增訂單和新增消息放到一個事務里完成,然后通過輪詢的方式去查詢消息表,將消息推送到MQ,庫存系統(tǒng)去消費MQ。
執(zhí)行流程:
- 訂單系統(tǒng),添加一條訂單和一條消息,在一個事務里提交
- 訂單系統(tǒng),使用定時任務輪詢查詢狀態(tài)為未同步的消息表,發(fā)送到MQ,如果發(fā)送失敗,就重試發(fā)送
- 庫存系統(tǒng),接收MQ消息,修改庫存表,需要保證冪等操作
- 如果修改成功,調用rpc接口修改訂單系統(tǒng)消息表的狀態(tài)為已完成或者直接刪除這條消息
- 如果修改失敗,可以不做處理,等待重試
訂單系統(tǒng)中的消息有可能由于業(yè)務問題會一直重復發(fā)送,所以為了避免這種情況可以記錄一下發(fā)送次數,當達到次數限制之后報警,人工接入處理;庫存系統(tǒng)需要保證冪等,避免同一條消息被多次消費造成數據一致。
本地消息表這種方案實現了最終一致性,需要在業(yè)務系統(tǒng)里增加消息表,業(yè)務邏輯中多一次插入的DB操作,所以性能會有損耗,而且最終一致性的間隔主要有定時任務的間隔時間決定。
2.3 MQ消息事務
消息事務的原理是將兩個事務通過消息中間件進行異步解耦。
訂單系統(tǒng)執(zhí)行自己的本地事務,并發(fā)送MQ消息,庫存系統(tǒng)接收消息,執(zhí)行自己的本地事務,乍一看,好像跟本地消息表的實現方案類似,只是省去了對本地消息表的操作和輪詢發(fā)送MQ的操作,但實際上兩種方案的實現是不一樣的。
消息事務一定要保證業(yè)務操作與消息發(fā)送的一致性,如果業(yè)務操作成功,這條消息也一定投遞成功。
消息事務依賴于消息中間件的事務消息,基于消息中間件的二階段提交實現的,RocketMQ就支持事務消息。
執(zhí)行流程:
- 發(fā)送prepare消息到消息中間件
- 發(fā)送成功后,執(zhí)行本地事務
- 如果事務執(zhí)行成功,則commit,消息中間件將消息下發(fā)至消費端
- 如果事務執(zhí)行失敗,則回滾,消息中間件將這條prepare消息刪除
- 消費端接收到消息進行消費,如果消費失敗,則不斷重試
這種方案也是實現了最終一致性,對比本地消息表實現方案,不需要再建消息表,不再依賴本地數據庫事務了,所以這種方案更適用于高并發(fā)的場景。
三. 總結
在實際生產中我們要盡量避免使用分布式事務,能轉化為本地事務就用本地事務,如果必須使用分布式事務,還需要從業(yè)務角度多思考使用哪種方案更適合。