淺談分布式事務(wù)
現(xiàn)今互聯(lián)網(wǎng)界,分布式系統(tǒng)和微服務(wù)架構(gòu)盛行。一個(gè)簡(jiǎn)單操作,在服務(wù)端非常可能是由多個(gè)服務(wù)和數(shù)據(jù)庫實(shí)例協(xié)同完成的。在一致性要求較高的場(chǎng)景下,多個(gè)獨(dú)立操作之間的一致性問題顯得格外棘手。
基于水平擴(kuò)容能力和成本考慮,傳統(tǒng)的強(qiáng)一致的解決方案(e.g.單機(jī)事務(wù))紛紛被拋棄。其理論依據(jù)就是響當(dāng)當(dāng)?shù)腃AP原理。往往為了可用性和分區(qū)容錯(cuò)性,忍痛放棄強(qiáng)一致支持,轉(zhuǎn)而追求最終一致性。
分布式系統(tǒng)的特性
在分布式系統(tǒng)中,同時(shí)滿足CAP定律中的一致性 Consistency、可用性 Availability和分區(qū)容錯(cuò)性 Partition Tolerance三者是不可能的。在絕大多數(shù)的場(chǎng)景,都需要犧牲強(qiáng)一致性來換取系統(tǒng)的高可用性,系統(tǒng)往往只需要保證最終一致性。
CAP理解:
- Consistency:強(qiáng)一致性就是在客戶端任何時(shí)候看到各節(jié)點(diǎn)的數(shù)據(jù)都是一致的(All nodes see the same data at the same time)。
- Availability:高可用性就是在任何時(shí)候都可以讀寫(Reads and writes always succeed)。
- Partition Tolerance:分區(qū)容錯(cuò)性是在網(wǎng)絡(luò)故障、某些節(jié)點(diǎn)不能通信的時(shí)候系統(tǒng)仍能繼續(xù)工作(The system continue to operate despite arbitrary message loss or failure of part of the the system)。以實(shí)際效果而言,分區(qū)相當(dāng)于對(duì)通信的時(shí)限要求。系統(tǒng)如果不能在時(shí)限內(nèi)達(dá)成數(shù)據(jù)一致性,就意味著發(fā)生了分區(qū)的情況,必須就當(dāng)前操作在C和A之間做出選擇。
ACID理解:
- Atomicity 原子性:一個(gè)事務(wù)中的所有操作,要么全部完成,要么全部不完成,不會(huì)結(jié)束在中間某個(gè)環(huán)節(jié)。事務(wù)在執(zhí)行過程中發(fā)生錯(cuò)誤,會(huì)被回滾到事務(wù)開始前的狀態(tài),就像這個(gè)事務(wù)從來沒有執(zhí)行過一樣。
- Consistency 一致性:在事務(wù)開始之前和事務(wù)結(jié)束以后,數(shù)據(jù)庫的完整性沒有被破壞。
- Isolation 隔離性:數(shù)據(jù)庫允許多個(gè)并發(fā)事務(wù)同時(shí)對(duì)其數(shù)據(jù)進(jìn)行讀寫和修改的能力,隔離性可以防止多個(gè)事務(wù)并發(fā)執(zhí)行時(shí)由于交叉執(zhí)行而導(dǎo)致數(shù)據(jù)的不一致。
- Durability 持久性:事務(wù)處理結(jié)束后,對(duì)數(shù)據(jù)的修改就是***的,即便系統(tǒng)故障也不會(huì)丟失。
分布式事務(wù)的基本介紹
分布式事務(wù)服務(wù)(Distributed Transaction Service,DTS)是一個(gè)分布式事務(wù)框架,用來保障在大規(guī)模分布式環(huán)境下事務(wù)的最終一致性。
CAP理論告訴我們?cè)诜植际酱鎯?chǔ)系統(tǒng)中,最多只能實(shí)現(xiàn)上面的兩點(diǎn)。而由于當(dāng)前的網(wǎng)絡(luò)硬件肯定會(huì)出現(xiàn)延遲丟包等問題,所以分區(qū)容忍性是我們必須需要實(shí)現(xiàn)的,所以我們只能在一致性和可用性之間進(jìn)行權(quán)衡。
為了保障系統(tǒng)的可用性,互聯(lián)網(wǎng)系統(tǒng)大多將強(qiáng)一致性需求轉(zhuǎn)換成最終一致性的需求,并通過系統(tǒng)執(zhí)行冪等性的保證,保證數(shù)據(jù)的最終一致性。
數(shù)據(jù)一致性理解:
- 強(qiáng)一致性:當(dāng)更新操作完成之后,任何多個(gè)后續(xù)進(jìn)程或者線程的訪問都會(huì)返回***的更新過的值。這種是對(duì)用戶最友好的,就是用戶上一次寫什么,下一次就保證能讀到什么。根據(jù) CAP 理論,這種實(shí)現(xiàn)需要犧牲可用性。
- 弱一致性:系統(tǒng)并不保證后續(xù)進(jìn)程或者線程的訪問都會(huì)返回***的更新過的值。系統(tǒng)在數(shù)據(jù)寫入成功之后,不承諾立即可以讀到***寫入的值,也不會(huì)具體的承諾多久之后可以讀到。
- 最終一致性:弱一致性的特定形式。系統(tǒng)保證在沒有后續(xù)更新的前提下,系統(tǒng)最終返回上一次更新操作的值。在沒有故障發(fā)生的前提下,不一致窗口的時(shí)間主要受通信延遲,系統(tǒng)負(fù)載和復(fù)制副本的個(gè)數(shù)影響。DNS 是一個(gè)典型的最終一致性系統(tǒng)。
常用的分布式技術(shù)說明
1. 本地消息表
這種實(shí)現(xiàn)方式的思路是源于ebay,其基本的設(shè)計(jì)思想是將遠(yuǎn)程分布式事務(wù)拆分成一系列的本地事務(wù)。
舉個(gè)經(jīng)典的跨行轉(zhuǎn)賬的例子來描述。
***步偽代碼如下,扣款1W,通過本地事務(wù)保證了憑證消息插入到消息表中。
- Begin transaction update A set amount = amount - 10000 where userId = 1;
- insert into message(userId, price, status) values(1, 10000, 1);
- End transaction commit;
第二步,通知對(duì)方銀行賬戶上加1W了,通常采用兩種方式:
采用時(shí)效性高的MQ,由對(duì)方訂閱消息并監(jiān)聽,有消息時(shí)自動(dòng)觸發(fā)事件。
采用定時(shí)輪詢掃描的方式,去檢查消息表的數(shù)據(jù)。
2. 消息中間件
非事務(wù)性的消息中間件
還是以上述提到的跨行轉(zhuǎn)賬為例,我們很難保證在扣款完成之后對(duì)MQ投遞消息的操作就一定能成功。這樣一致性似乎很難保證。
- try {
- bool result = dao.update(model); // 操作數(shù)據(jù)庫失敗,會(huì)拋出異常
- if (result) {
- mq.send(model); // 如果mq方式執(zhí)行失敗,會(huì)拋出異常
- } } catch(Exception e) {
- rollback(); // 如果發(fā)生異常,則回滾 }
我們來分析下可能的情況:
- 操作數(shù)據(jù)庫成功,向MQ中投遞消息也成功,皆大歡喜。
- 操作數(shù)據(jù)庫失敗,不會(huì)向MQ中投遞消息了。
- 操作數(shù)據(jù)庫成功,但是向MQ中投遞消息時(shí)失敗,向外拋出了異常,剛剛執(zhí)行的更新數(shù)據(jù)庫的操作將被回滾。
從上面分析的幾種情況來看,基本上能保證發(fā)送者發(fā)送消息的可靠性。我們?cè)賮矸治鱿孪M(fèi)者端面臨的問題:
- 消息出列后,消費(fèi)者對(duì)應(yīng)的業(yè)務(wù)操作要執(zhí)行成功。如果業(yè)務(wù)執(zhí)行失敗,消息不能失效或者丟失。需要保證消息與業(yè)務(wù)操作一致。
- 盡量避免消息重復(fù)消費(fèi)。如果重復(fù)消費(fèi),也不能因此影響業(yè)務(wù)結(jié)果。
支持事務(wù)的消息中間件
除了上面介紹的通過異常捕獲和回滾的方式外,還有沒有其他的思路呢?
阿里巴巴的RocketMQ中間件就支持一種事務(wù)消息機(jī)制,能夠確保本地操作和發(fā)送消息達(dá)到本地事務(wù)一樣的效果。
- ***階段,RocketMQ在執(zhí)行本地事務(wù)之前,會(huì)先發(fā)送一個(gè)Prepared消息,并且會(huì)持有這個(gè)消息的地址。
- 第二階段,執(zhí)行本地事物操作。
- 第三階段,確認(rèn)消息發(fā)送,通過***階段拿到的地址去訪問消息,并修改狀態(tài),如果本地事務(wù)成功,則修改狀態(tài)為已提交,否則修改狀態(tài)為已回滾。
但是如果第三階段的確認(rèn)消息發(fā)送失敗了怎么辦?RocketMQ會(huì)定期掃描消息集群中的事物消息,如果發(fā)現(xiàn)了prepare狀態(tài)的消息,它會(huì)向消息發(fā)送者確認(rèn)本地事務(wù)是否已執(zhí)行成功,如果成功是回滾還是繼續(xù)發(fā)送確認(rèn)消息呢。RocketMQ會(huì)根據(jù)發(fā)送端設(shè)置的策略來決定是回滾還是繼續(xù)發(fā)送確認(rèn)消息。這樣就保證了消息發(fā)送與本地事務(wù)同時(shí)成功或同時(shí)失敗。
目前主流的開源MQ(ActiveMQ、RabbitMQ、Kafka)均未實(shí)現(xiàn)對(duì)事務(wù)消息的支持,比較遺憾的是,RocketMQ事務(wù)消息部分的代碼也并未開源,需要自己去實(shí)現(xiàn)。
理解2PC和3PC協(xié)議
為了解決分布式一致性問題,前人在性能和數(shù)據(jù)一致性的反反復(fù)復(fù)權(quán)衡過程中總結(jié)了許多典型的協(xié)議和算法。其中比較著名的有二階提交協(xié)議(2 Phase Commitment Protocol),三階提交協(xié)議(3 Phase Commitment Protocol)。
2PC
分布式事務(wù)最常用的解決方案就是二階段提交。在分布式系統(tǒng)中,每個(gè)節(jié)點(diǎn)雖然可以知曉自己的操作時(shí)成功或者失敗,卻無法知道其他節(jié)點(diǎn)的操作的成功或失敗。當(dāng)一個(gè)事務(wù)跨越多個(gè)節(jié)點(diǎn)時(shí),為了保持事務(wù)的ACID特性,需要引入一個(gè)作為協(xié)調(diào)者的組件來統(tǒng)一掌控所有參與者節(jié)點(diǎn)的操作結(jié)果并最終指示這些節(jié)點(diǎn)是否要把操作結(jié)果進(jìn)行真正的提交。
因此,二階段提交的算法思路可以概括為:參與者將操作成敗通知協(xié)調(diào)者,再由協(xié)調(diào)者根據(jù)所有參與者的反饋情報(bào)決定各參與者是否要提交操作還是中止操作。
所謂的兩個(gè)階段是指:***階段:準(zhǔn)備階段(投票階段)和第二階段:提交階段(執(zhí)行階段)。
***階段:投票階段
該階段的主要目的在于打探數(shù)據(jù)庫集群中的各個(gè)參與者是否能夠正常的執(zhí)行事務(wù),具體步驟如下:
1. 協(xié)調(diào)者向所有的參與者發(fā)送事務(wù)執(zhí)行請(qǐng)求,并等待參與者反饋事務(wù)執(zhí)行結(jié)果。
2. 事務(wù)參與者收到請(qǐng)求之后,執(zhí)行事務(wù),但不提交,并記錄事務(wù)日志。
3. 參與者將自己事務(wù)執(zhí)行情況反饋給協(xié)調(diào)者,同時(shí)阻塞等待協(xié)調(diào)者的后續(xù)指令。
第二階段:事務(wù)提交階段
在***階段協(xié)調(diào)者的詢盤之后,各個(gè)參與者會(huì)回復(fù)自己事務(wù)的執(zhí)行情況,這時(shí)候存在三種可能:
- 所有的參與者回復(fù)能夠正常執(zhí)行事務(wù)。
- 一個(gè)或多個(gè)參與者回復(fù)事務(wù)執(zhí)行失敗。
- 協(xié)調(diào)者等待超時(shí)。
對(duì)于***種情況,協(xié)調(diào)者將向所有的參與者發(fā)出提交事務(wù)的通知,具體步驟如下:
- 協(xié)調(diào)者向各個(gè)參與者發(fā)送commit通知,請(qǐng)求提交事務(wù)。
- 參與者收到事務(wù)提交通知之后,執(zhí)行commit操作,然后釋放占有的資源。
- 參與者向協(xié)調(diào)者返回事務(wù)commit結(jié)果信息。
對(duì)于第二、三種情況,協(xié)調(diào)者均認(rèn)為參與者無法正常成功執(zhí)行事務(wù),為了整個(gè)集群數(shù)據(jù)的一致性,所以要向各個(gè)參與者發(fā)送事務(wù)回滾通知,具體步驟如下:
- 協(xié)調(diào)者向各個(gè)參與者發(fā)送事務(wù)rollback通知,請(qǐng)求回滾事務(wù)。
- 參與者收到事務(wù)回滾通知之后,執(zhí)行rollback操作,然后釋放占有的資源。
- 參與者向協(xié)調(diào)者返回事務(wù)rollback結(jié)果信息。
兩階段提交協(xié)議解決的是分布式數(shù)據(jù)庫數(shù)據(jù)強(qiáng)一致性問題,其原理簡(jiǎn)單,易于實(shí)現(xiàn),但是缺點(diǎn)也是顯而易見的,主要缺點(diǎn)如下:
- 單點(diǎn)問題:協(xié)調(diào)者在整個(gè)兩階段提交過程中扮演著舉足輕重的作用,一旦協(xié)調(diào)者所在服務(wù)器宕機(jī),那么就會(huì)影響整個(gè)數(shù)據(jù)庫集群的正常運(yùn)行,比如在第二階段中,如果協(xié)調(diào)者因?yàn)楣收喜荒苷0l(fā)送事務(wù)提交或回滾通知,那么參與者們將一直處于阻塞狀態(tài),整個(gè)數(shù)據(jù)庫集群將無法提供服務(wù)。
- 同步阻塞:兩階段提交執(zhí)行過程中,所有的參與者都需要聽從協(xié)調(diào)者的統(tǒng)一調(diào)度,期間處于阻塞狀態(tài)而不能從事其他操作,這樣效率及其低下。
- 數(shù)據(jù)不一致性:兩階段提交協(xié)議雖然為分布式數(shù)據(jù)強(qiáng)一致性所設(shè)計(jì),但仍然存在數(shù)據(jù)不一致性的可能,比如在第二階段中,假設(shè)協(xié)調(diào)者發(fā)出了事務(wù)commit的通知,但是因?yàn)榫W(wǎng)絡(luò)問題該通知僅被一部分參與者所收到并執(zhí)行了commit操作,其余的參與者則因?yàn)闆]有收到通知一直處于阻塞狀態(tài),這時(shí)候就產(chǎn)生了數(shù)據(jù)的不一致性。
3PC
針對(duì)兩階段提交存在的問題,三階段提交協(xié)議通過引入一個(gè)“預(yù)詢盤”階段,以及超時(shí)策略來減少整個(gè)集群的阻塞時(shí)間,提升系統(tǒng)性能。三階段提交的三個(gè)階段分別為:can_commit,pre_commit,do_commit。
***階段:can_commit
該階段協(xié)調(diào)者會(huì)去詢問各個(gè)參與者是否能夠正常執(zhí)行事務(wù),參與者根據(jù)自身情況回復(fù)一個(gè)預(yù)估值,相對(duì)于真正的執(zhí)行事務(wù),這個(gè)過程是輕量的,具體步驟如下:
協(xié)調(diào)者向各個(gè)參與者發(fā)送事務(wù)詢問通知,詢問是否可以執(zhí)行事務(wù)操作,并等待回復(fù)。
各個(gè)參與者依據(jù)自身狀況回復(fù)一個(gè)預(yù)估值,如果預(yù)估自己能夠正常執(zhí)行事務(wù)就返回確定信息,并進(jìn)入預(yù)備狀態(tài),否則返回否定信息。
第二階段:pre_commit
本階段協(xié)調(diào)者會(huì)根據(jù)***階段的詢盤結(jié)果采取相應(yīng)操作,詢盤結(jié)果主要有三種:
- 所有的參與者都返回確定信息。
- 一個(gè)或多個(gè)參與者返回否定信息。
- 協(xié)調(diào)者等待超時(shí)。
針對(duì)***種情況,協(xié)調(diào)者會(huì)向所有參與者發(fā)送事務(wù)執(zhí)行請(qǐng)求,具體步驟如下:
- 協(xié)調(diào)者向所有的事務(wù)參與者發(fā)送事務(wù)執(zhí)行通知。
- 參與者收到通知后,執(zhí)行事務(wù),但不提交。
- 參與者將事務(wù)執(zhí)行情況返回給客戶端。
在上面的步驟中,如果參與者等待超時(shí),則會(huì)中斷事務(wù)。 針對(duì)第二、三種情況,協(xié)調(diào)者認(rèn)為事務(wù)無法正常執(zhí)行,于是向各個(gè)參與者發(fā)出abort通知,請(qǐng)求退出預(yù)備狀態(tài),具體步驟如下:
協(xié)調(diào)者向所有事務(wù)參與者發(fā)送abort通知
參與者收到通知后,中斷事務(wù)
第三階段:do_commit
如果第二階段事務(wù)未中斷,那么本階段協(xié)調(diào)者將會(huì)依據(jù)事務(wù)執(zhí)行返回的結(jié)果來決定提交或回滾事務(wù),分為三種情況:
- 所有的參與者都能正常執(zhí)行事務(wù)。
- 一個(gè)或多個(gè)參與者執(zhí)行事務(wù)失敗。
- 協(xié)調(diào)者等待超時(shí)。
針對(duì)***種情況,協(xié)調(diào)者向各個(gè)參與者發(fā)起事務(wù)提交請(qǐng)求,具體步驟如下:
- 協(xié)調(diào)者向所有參與者發(fā)送事務(wù)commit通知。
- 所有參與者在收到通知之后執(zhí)行commit操作,并釋放占有的資源。
- 參與者向協(xié)調(diào)者反饋事務(wù)提交結(jié)果。
針對(duì)第二、三種情況,協(xié)調(diào)者認(rèn)為事務(wù)無法正常執(zhí)行,于是向各個(gè)參與者發(fā)送事務(wù)回滾請(qǐng)求,具體步驟如下:
- 協(xié)調(diào)者向所有參與者發(fā)送事務(wù)rollback通知。
- 所有參與者在收到通知之后執(zhí)行rollback操作,并釋放占有的資源。
- 參與者向協(xié)調(diào)者反饋事務(wù)提交結(jié)果。
在本階段如果因?yàn)閰f(xié)調(diào)者或網(wǎng)絡(luò)問題,導(dǎo)致參與者遲遲不能收到來自協(xié)調(diào)者的commit或rollback請(qǐng)求,那么參與者將不會(huì)如兩階段提交中那樣陷入阻塞,而是等待超時(shí)后繼續(xù)commit。相對(duì)于兩階段提交雖然降低了同步阻塞,但仍然無法避免數(shù)據(jù)的不一致性