分布式系统事务一致性解决方案
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency
開篇
在OLTP系統(tǒng)領(lǐng)域,我們?cè)诤芏鄻I(yè)務(wù)場(chǎng)景下都會(huì)面臨事務(wù)一致性方面的需求,例如最經(jīng)典的Bob給Smith轉(zhuǎn)賬的案例。傳統(tǒng)的企業(yè)開發(fā),系統(tǒng)往往是以單體應(yīng)用形式存在的,也沒(méi)有橫跨多個(gè)數(shù)據(jù)庫(kù)。我們通常只需借助開發(fā)平臺(tái)中特有數(shù)據(jù)訪問(wèn)技術(shù)和框架(例如Spring、JDBC、ADO.NET),結(jié)合關(guān)系型數(shù)據(jù)庫(kù)自帶的事務(wù)管理機(jī)制來(lái)實(shí)現(xiàn)事務(wù)性的需求。關(guān)系型數(shù)據(jù)庫(kù)通常具有ACID特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)。
而大型互聯(lián)網(wǎng)平臺(tái)往往是由一系列分布式系統(tǒng)構(gòu)成的,開發(fā)語(yǔ)言平臺(tái)和技術(shù)棧也相對(duì)比較雜,尤其是在SOA和微服務(wù)架構(gòu)盛行的今天,一個(gè)看起來(lái)簡(jiǎn)單的功能,內(nèi)部可能需要調(diào)用多個(gè)“服務(wù)”并操作多個(gè)數(shù)據(jù)庫(kù)或分片來(lái)實(shí)現(xiàn),情況往往會(huì)復(fù)雜很多。單一的技術(shù)手段和解決方案,已經(jīng)無(wú)法應(yīng)對(duì)和滿足這些復(fù)雜的場(chǎng)景了。
分布式系統(tǒng)的特性
對(duì)分布式系統(tǒng)有過(guò)研究的讀者,可能聽說(shuō)過(guò)“CAP定律”、“Base理論”等,非常巧的是,化學(xué)理論中ACID是酸、Base恰好是堿。這里筆者不對(duì)這些概念做過(guò)多的解釋,有興趣的讀者可以查看相關(guān)參考資料。CAP定律如下圖:
?
在分布式系統(tǒng)中,同時(shí)滿足“CAP定律”中的“一致性”、“可用性”和“分區(qū)容錯(cuò)性”三者是不可能的,這比現(xiàn)實(shí)中找對(duì)象需同時(shí)滿足“高、富、帥”或“白、富、美”更加困難。在互聯(lián)網(wǎng)領(lǐng)域的絕大多數(shù)的場(chǎng)景,都需要犧牲強(qiáng)一致性來(lái)?yè)Q取系統(tǒng)的高可用性,系統(tǒng)往往只需要保證“最終一致性”,只要這個(gè)最終時(shí)間是在用戶可以接受的范圍內(nèi)即可。
分布式事務(wù)
提到分布式系統(tǒng),必然要提到分布式事務(wù)。要想理解分布式事務(wù),不得不先介紹一下兩階段提交協(xié)議。先舉個(gè)簡(jiǎn)單但不精準(zhǔn)的例子來(lái)說(shuō)明:
第一階段,張老師作為“協(xié)調(diào)者”,給小強(qiáng)和小明(參與者、節(jié)點(diǎn))發(fā)微信,組織他們倆明天8點(diǎn)在學(xué)校門口集合,一起去爬山,然后開始等待小強(qiáng)和小明答復(fù)。
第二階段,如果小強(qiáng)和小明都回答沒(méi)問(wèn)題,那么大家如約而至。如果小強(qiáng)或者小明其中一人回答說(shuō)“明天沒(méi)空,不行”,那么張老師會(huì)立即通知小強(qiáng)和小明“爬山活動(dòng)取消”。
細(xì)心的讀者會(huì)發(fā)現(xiàn),這個(gè)過(guò)程中可能有很多問(wèn)題的。如果小強(qiáng)沒(méi)看手機(jī),那么張老師會(huì)一直等著答復(fù),小明可能在家里把爬山裝備都準(zhǔn)備好了卻一直等著張老師確認(rèn)信息。更嚴(yán)重的是,如果到明天8點(diǎn)小強(qiáng)還沒(méi)有答復(fù),那么就算“超時(shí)”了,那小明到底去還是不去集合爬山呢?
這就是兩階段提交協(xié)議的弊病,所以后來(lái)業(yè)界又引入了三階段提交協(xié)議來(lái)解決該類問(wèn)題。
兩階段提交協(xié)議在主流開發(fā)語(yǔ)言平臺(tái),數(shù)據(jù)庫(kù)產(chǎn)品中都有廣泛應(yīng)用和實(shí)現(xiàn)的,下面來(lái)介紹一下XOpen組織提供的DTP模型圖:
XA協(xié)議指的是TM(事務(wù)管理器)和RM(資源管理器)之間的接口。目前主流的關(guān)系型數(shù)據(jù)庫(kù)產(chǎn)品都是實(shí)現(xiàn)了XA接口的。JTA(Java Transaction API)是符合X/Open DTP模型的,事務(wù)管理器和資源管理器之間也使用了XA協(xié)議。 本質(zhì)上也是借助兩階段提交協(xié)議來(lái)實(shí)現(xiàn)分布式事務(wù)的,下面分別來(lái)看看XA事務(wù)成功和失敗的模型圖:
在JavaEE平臺(tái)下,WebLogic、Webshare等主流商用的應(yīng)用服務(wù)器提供了JTA的實(shí)現(xiàn)和支持。而在Tomcat下是沒(méi)有實(shí)現(xiàn)的(其實(shí)筆者并不認(rèn)為Tomcat能算是JavaEE應(yīng)用服務(wù)器),這就需要借助第三方的框架Jotm、Automikos等來(lái)實(shí)現(xiàn),兩者均支持spring事務(wù)整合。
而在Windows .NET平臺(tái)中,則可以借助ado.net中的TransactionScop?API來(lái)編程實(shí)現(xiàn),還必須配置和借助Windows操作系統(tǒng)中的MSDTC服務(wù)。如果你的數(shù)據(jù)庫(kù)使用的mysql,并且mysql是部署在Linux平臺(tái)上的,那么是無(wú)法支持分布式事務(wù)的。 由于篇幅關(guān)系,這里不展開,感興趣的讀者可以自行查閱相關(guān)資料并實(shí)踐。
總結(jié):這種方式實(shí)現(xiàn)難度不算太高,比較適合傳統(tǒng)的單體應(yīng)用,在同一個(gè)方法中存在跨庫(kù)操作的情況。但分布式事務(wù)對(duì)性能的影響會(huì)比較大,不適合高并發(fā)和高性能要求的場(chǎng)景。
提供回滾接口
在服務(wù)化架構(gòu)中,功能X,需要去協(xié)調(diào)后端的A、B甚至更多的原子服務(wù)。那么問(wèn)題來(lái)了,假如A和B其中一個(gè)調(diào)用失敗了,那可怎么辦呢?
在筆者的工作中經(jīng)常遇到這類問(wèn)題,往往提供了一個(gè)BFF層來(lái)協(xié)調(diào)調(diào)用A、B服務(wù)。如果有些是需要同步返回結(jié)果的,我會(huì)盡量按照“串行”的方式去調(diào)用。如果調(diào)用A失敗,則不會(huì)盲目去調(diào)用B。如果調(diào)用A成功,而調(diào)用B失敗,會(huì)嘗試去回滾剛剛對(duì)A的調(diào)用操作。
當(dāng)然,有些時(shí)候我們不必嚴(yán)格提供單獨(dú)對(duì)應(yīng)的回滾接口,可以通過(guò)傳遞參數(shù)巧妙的實(shí)現(xiàn)。
這樣的情況,我們會(huì)盡量把可提供回滾接口的服務(wù)放在前面。舉個(gè)例子說(shuō)明:
我們的某個(gè)論壇網(wǎng)站,每天登錄成功后會(huì)獎(jiǎng)勵(lì)用戶5個(gè)積分,但是積分和用戶又是兩套獨(dú)立的子系統(tǒng)服務(wù),對(duì)應(yīng)不同的DB,這控制起來(lái)就比較麻煩了。解決思路:
總結(jié):這種方式缺點(diǎn)比較多,通常在復(fù)雜場(chǎng)景下是不推薦使用的,除非是非常簡(jiǎn)單的場(chǎng)景,非常容易提供回滾,而且依賴的服務(wù)也非常少的情況。
?
這種實(shí)現(xiàn)方式會(huì)造成代碼量龐大,耦合性高。而且非常有局限性,因?yàn)橛泻芏嗟臉I(yè)務(wù)是無(wú)法很簡(jiǎn)單的實(shí)現(xiàn)回滾的,如果串行的服務(wù)很多,回滾的成本實(shí)在太高。
本地消息表
這種實(shí)現(xiàn)方式的思路,其實(shí)是源于ebay,后來(lái)通過(guò)支付寶等公司的布道,在業(yè)內(nèi)廣泛使用。其基本的設(shè)計(jì)思想是將遠(yuǎn)程分布式事務(wù)拆分成一系列的本地事務(wù)。如果不考慮性能及設(shè)計(jì)優(yōu)雅,借助關(guān)系型數(shù)據(jù)庫(kù)中的表即可實(shí)現(xiàn)。
舉個(gè)經(jīng)典的跨行轉(zhuǎn)賬的例子來(lái)描述。
第一步偽代碼如下,扣款1W,通過(guò)本地事務(wù)保證了憑證消息插入到消息表中。
第二步,通知對(duì)方銀行賬戶上加1W了。那問(wèn)題來(lái)了,如何通知到對(duì)方呢?
通常采用兩種方式:
兩種方式其實(shí)各有利弊,僅僅依靠MQ,可能會(huì)出現(xiàn)通知失敗的問(wèn)題。而過(guò)于頻繁的定時(shí)輪詢,效率也不是最佳的(90%是無(wú)用功)。所以,我們一般會(huì)把兩種方式結(jié)合起來(lái)使用。
解決了通知的問(wèn)題,又有新的問(wèn)題了。萬(wàn)一這消息有重復(fù)被消費(fèi),往用戶帳號(hào)上多加了錢,那豈不是后果很嚴(yán)重?
仔細(xì)思考,其實(shí)我們可以消息消費(fèi)方,也通過(guò)一個(gè)“消費(fèi)狀態(tài)表”來(lái)記錄消費(fèi)狀態(tài)。在執(zhí)行“加款”操作之前,檢測(cè)下該消息(提供標(biāo)識(shí))是否已經(jīng)消費(fèi)過(guò),消費(fèi)完成后,通過(guò)本地事務(wù)控制來(lái)更新這個(gè)“消費(fèi)狀態(tài)表”。這樣子就避免重復(fù)消費(fèi)的問(wèn)題。
總結(jié):上訴的方式是一種非常經(jīng)典的實(shí)現(xiàn),基本避免了分布式事務(wù),實(shí)現(xiàn)了“最終一致性”。但是,關(guān)系型數(shù)據(jù)庫(kù)的吞吐量和性能方面存在瓶頸,頻繁的讀寫消息會(huì)給數(shù)據(jù)庫(kù)造成壓力。所以,在真正的高并發(fā)場(chǎng)景下,該方案也會(huì)有瓶頸和限制的。
MQ(非事務(wù)消息)
通常情況下,在使用非事務(wù)消息支持的MQ產(chǎn)品時(shí),我們很難將業(yè)務(wù)操作與對(duì)MQ的操作放在一個(gè)本地事務(wù)域中管理。通俗點(diǎn)描述,還是以上述提到的“跨行轉(zhuǎn)賬”為例,我們很難保證在扣款完成之后對(duì)MQ投遞消息的操作就一定能成功。這樣一致性似乎很難保證。
先從消息生產(chǎn)者這端來(lái)分析,請(qǐng)看偽代碼:
根據(jù)上述代碼及注釋,我們來(lái)分析下可能的情況:
從上面分析的幾種情況來(lái)看,貌似問(wèn)題都不大的。那么我們來(lái)分析下消費(fèi)者端面臨的問(wèn)題:
如何保證消息與業(yè)務(wù)操作一致,不丟失?
主流的MQ產(chǎn)品都具有持久化消息的功能。如果消費(fèi)者宕機(jī)或者消費(fèi)失敗,都可以執(zhí)行重試機(jī)制的(有些MQ可以自定義重試次數(shù))。
如何避免消息被重復(fù)消費(fèi)造成的問(wèn)題?
?
總結(jié):這種方式比較常見,性能和吞吐量是優(yōu)于使用關(guān)系型數(shù)據(jù)庫(kù)消息表的方案。如果MQ自身和業(yè)務(wù)都具有高可用性,理論上是可以滿足大部分的業(yè)務(wù)場(chǎng)景的。不過(guò)在沒(méi)有充分測(cè)試的情況下,不建議在交易業(yè)務(wù)中直接使用。
MQ(事務(wù)消息)
舉個(gè)例子,Bob向Smith轉(zhuǎn)賬,那我們到底是先發(fā)送消息,還是先執(zhí)行扣款操作?
好像都可能會(huì)出問(wèn)題。如果先發(fā)消息,扣款操作失敗,那么Smith的賬戶里面會(huì)多出一筆錢。反過(guò)來(lái),如果先執(zhí)行扣款操作,后發(fā)送消息,那有可能扣款成功了但是消息沒(méi)發(fā)出去,Smith收不到錢。除了上面介紹的通過(guò)異常捕獲和回滾的方式外,還有沒(méi)有其他的思路呢?
下面以阿里巴巴的RocketMQ中間件為例,分析下其設(shè)計(jì)和實(shí)現(xiàn)思路。
RocketMQ第一階段發(fā)送Prepared消息時(shí),會(huì)拿到消息的地址,第二階段執(zhí)行本地事物,第三階段通過(guò)第一階段拿到的地址去訪問(wèn)消息,并修改狀態(tài)。細(xì)心的讀者可能又發(fā)現(xiàn)問(wèn)題了,如果確認(rèn)消息發(fā)送失敗了怎么辦?RocketMQ會(huì)定期掃描消息集群中的事物消息,這時(shí)候發(fā)現(xiàn)了Prepared消息,它會(huì)向消息發(fā)送者確認(rèn),Bob的錢到底是減了還是沒(méi)減呢?如果減了是回滾還是繼續(xù)發(fā)送確認(rèn)消息呢?RocketMQ會(huì)根據(jù)發(fā)送端設(shè)置的策略來(lái)決定是回滾還是繼續(xù)發(fā)送確認(rèn)消息。這樣就保證了消息發(fā)送與本地事務(wù)同時(shí)成功或同時(shí)失敗。如下圖:
總結(jié):據(jù)筆者的了解,各大知名的電商平臺(tái)和互聯(lián)網(wǎng)公司,幾乎都是采用類似的設(shè)計(jì)思路來(lái)實(shí)現(xiàn)“最終一致性”的。這種方式適合的業(yè)務(wù)場(chǎng)景廣泛,而且比較可靠。不過(guò)這種方式技術(shù)實(shí)現(xiàn)的難度比較大。目前主流的開源MQ(ActiveMQ、RabbitMQ、Kafka)均未實(shí)現(xiàn)對(duì)事務(wù)消息的支持,所以需二次開發(fā)或者新造輪子。比較遺憾的是,RocketMQ事務(wù)消息部分的代碼也并未開源,需要自己去實(shí)現(xiàn)。
其他補(bǔ)償方式
做過(guò)支付寶交易接口的同學(xué)都知道,我們一般會(huì)在支付寶的回調(diào)頁(yè)面和接口里,解密參數(shù),然后調(diào)用系統(tǒng)中更新交易狀態(tài)相關(guān)的服務(wù),將訂單更新為付款成功。同時(shí),只有當(dāng)我們回調(diào)頁(yè)面中輸出了success字樣或者標(biāo)識(shí)業(yè)務(wù)處理成功相應(yīng)狀態(tài)碼時(shí),支付寶才會(huì)停止回調(diào)請(qǐng)求。否則,支付寶會(huì)每間隔一段時(shí)間后,再向客戶方發(fā)起回調(diào)請(qǐng)求,直到輸出成功標(biāo)識(shí)為止。
其實(shí)這就是一個(gè)很典型的補(bǔ)償例子,跟一些MQ重試補(bǔ)償機(jī)制很類似。
一般成熟的系統(tǒng)中,對(duì)于級(jí)別較高的服務(wù)和接口,整體的可用性通常都會(huì)很高。如果有些業(yè)務(wù)由于瞬時(shí)的網(wǎng)絡(luò)故障或調(diào)用超時(shí)等問(wèn)題,那么這種重試機(jī)制其實(shí)是非常有效的。
當(dāng)然,考慮個(gè)比較極端的場(chǎng)景,假如系統(tǒng)自身有bug或者程序邏輯有問(wèn)題,那么重試1W次那也是無(wú)濟(jì)于事的。那豈不是就發(fā)生了“明明已經(jīng)付款,卻顯示未付款不發(fā)貨”類似的悲劇?
其實(shí)為了交易系統(tǒng)更可靠,我們一般會(huì)在類似交易這種高級(jí)別的服務(wù)代碼中,加入詳細(xì)日志記錄的,一旦系統(tǒng)內(nèi)部引發(fā)類似致命異常,會(huì)有郵件通知。同時(shí),后臺(tái)會(huì)有定時(shí)任務(wù)掃描和分析此類日志,檢查出這種特殊的情況,會(huì)嘗試通過(guò)程序來(lái)補(bǔ)償并郵件通知相關(guān)人員。
在某些特殊的情況下,還會(huì)有“人工補(bǔ)償”的,這也是最后一道屏障。
小結(jié)
上訴的幾種方案中,筆者也大致總結(jié)了其設(shè)計(jì)思路,優(yōu)勢(shì),劣勢(shì)等,相信讀者已經(jīng)有了一定的理解。其實(shí)分布式系統(tǒng)的事務(wù)一致性本身是一個(gè)技術(shù)難題,目前沒(méi)有一種很簡(jiǎn)單很完美的方案能夠應(yīng)對(duì)所有場(chǎng)景。具體還是要使用者根據(jù)不同的業(yè)務(wù)場(chǎng)景去抉擇。
轉(zhuǎn)載于:https://my.oschina.net/xiaominmin/blog/1615228
總結(jié)
以上是生活随笔為你收集整理的分布式系统事务一致性解决方案的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 2018年1月29日
- 下一篇: DuiC 统一配置管理 2