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