微服務(wù)下的數(shù)據(jù)一致性的幾種實(shí)現(xiàn)方式
1. 傳統(tǒng)應(yīng)用的事務(wù)管理
1.1 本地事務(wù)
再介紹微服務(wù)下的數(shù)據(jù)一致性之前,先簡(jiǎn)單地介紹一下事務(wù)的背景。傳統(tǒng)單機(jī)應(yīng)用使用一個(gè)RDBMS作為數(shù)據(jù)源。應(yīng)用開(kāi)啟事務(wù),進(jìn)行CRUD,提交或回滾事務(wù),統(tǒng)統(tǒng)發(fā)生在本地事務(wù)中,由資源管理器(RM)直接提供事務(wù)支持。數(shù)據(jù)的一致性在一個(gè)本地事務(wù)中得到保證。

1.2 分布式事務(wù)
1.2.1 兩階段提交(2PC)
當(dāng)應(yīng)用逐漸擴(kuò)展,出現(xiàn)一個(gè)應(yīng)用使用多個(gè)數(shù)據(jù)源的情況,這個(gè)時(shí)候本地事務(wù)已經(jīng)無(wú)法滿(mǎn)足數(shù)據(jù)一致性的要求。由于多個(gè)數(shù)據(jù)源的同時(shí)訪(fǎng)問(wèn),事務(wù)需要跨多個(gè)數(shù)據(jù)源管理,分布式事務(wù)應(yīng)運(yùn)而生。其中最流行的就是兩階段提交(2PC),分布式事務(wù)由事務(wù)管理器(TM)統(tǒng)一管理。
兩階段提交分為準(zhǔn)備階段和提交階段。

兩階段提交-commit

兩階段提交-rollback
然而兩階段提交也不能完全保證數(shù)據(jù)一致性問(wèn)題,并且有同步阻塞的問(wèn)題,所以其優(yōu)化版本三階段提交(3PC)被發(fā)明了出來(lái)。
1.2.2 三階段提交(3PC)

三階段提交
然而3PC也只能保證絕大多數(shù)情況下的數(shù)據(jù)一致性。
具體分布式事務(wù)2PC和3PC的詳細(xì)介紹請(qǐng)見(jiàn) ?關(guān)于分布式事務(wù)、兩階段提交協(xié)議、三階提交協(xié)議
。分布式事務(wù)不是本文的重點(diǎn),故不展開(kāi)。
2. 微服務(wù)下的事務(wù)管理
那么,分布式事務(wù)2PC或者3PC是否適合于微服務(wù)下的事務(wù)管理呢?答案是否定的,原因有三點(diǎn):
由于微服務(wù)間無(wú)法直接進(jìn)行數(shù)據(jù)訪(fǎng)問(wèn),微服務(wù)間互相調(diào)用通常通過(guò)RPC(dubbo)或Http API(SpringCloud)進(jìn)行,所以已經(jīng)無(wú)法使用TM統(tǒng)一管理微服務(wù)的RM。
不同的微服務(wù)使用的數(shù)據(jù)源類(lèi)型可能完全不同,如果微服務(wù)使用了NoSQL之類(lèi)不支持事務(wù)的數(shù)據(jù)庫(kù),則事務(wù)根本無(wú)從談起。
即使微服務(wù)使用的數(shù)據(jù)源都支持事務(wù),那么如果使用一個(gè)大事務(wù)將許多微服務(wù)的事務(wù)管理起來(lái),這個(gè)大事務(wù)維持的時(shí)間,將比本地事務(wù)長(zhǎng)幾個(gè)數(shù)量級(jí)。如此長(zhǎng)時(shí)間的事務(wù)及跨服務(wù)的事務(wù),將為產(chǎn)生很多鎖及數(shù)據(jù)不可用,嚴(yán)重影響系統(tǒng)性能。
由此可見(jiàn),傳統(tǒng)的分布式事務(wù)已經(jīng)無(wú)法滿(mǎn)足微服務(wù)架構(gòu)下的事務(wù)管理需求。那么,既然無(wú)法滿(mǎn)足傳統(tǒng)的ACID事務(wù),在微服務(wù)下的事務(wù)管理必然要遵循新的法則--BASE理論。
BASE理論由eBay的架構(gòu)師Dan
Pritchett提出,BASE理論是對(duì)CAP理論的延伸,核心思想是即使無(wú)法做到強(qiáng)一致性,應(yīng)用應(yīng)該可以采用合適的方式達(dá)到最終一致性。BASE是指基本可用(Basically
Available)、軟狀態(tài)( Soft State)、最終一致性( Eventual Consistency)。
基本可用?:指分布式系統(tǒng)在出現(xiàn)故障的時(shí)候,允許損失部分可用性,即保證核心可用。
軟狀態(tài)
:允許系統(tǒng)存在中間狀態(tài),而該中間狀態(tài)不會(huì)影響系統(tǒng)整體可用性。分布式存儲(chǔ)中一般一份數(shù)據(jù)至少會(huì)有三個(gè)副本,允許不同節(jié)點(diǎn)間副本同步的延時(shí)就是軟狀態(tài)的體現(xiàn)。
最終一致性?:最終一致性是指系統(tǒng)中的所有數(shù)據(jù)副本經(jīng)過(guò)一定時(shí)間后,最終能夠達(dá)到一致的狀態(tài)。弱一致性和強(qiáng)一致性相反,最終一致性是弱一致性的一種特殊情況。
BASE中的?最終一致性
是對(duì)于微服務(wù)下的事務(wù)管理的根本要求,既基于微服務(wù)的事務(wù)管理無(wú)法達(dá)到強(qiáng)一致性,但必須保證最重一致性。那么,有哪些方法可以保證微服務(wù)下的事務(wù)管理的最終一致性呢,按照實(shí)現(xiàn)原理分主要有兩類(lèi),事件通知型和補(bǔ)償型,其中事件通知型又可分為可靠事件通知模式及最大努力通知模式,而補(bǔ)償型又可分為T(mén)CC模式、和業(yè)務(wù)補(bǔ)償模式兩種。這四種模式都可以達(dá)到微服務(wù)下的數(shù)據(jù)最終一致性。
3. 實(shí)現(xiàn)微服務(wù)下數(shù)據(jù)一致性的方式
3.1 可靠事件通知模式
3.1.1 同步事件
可靠事件通知模式的設(shè)計(jì)理念比較容易理解,即是主服務(wù)完成后將結(jié)果通過(guò)事件(常常是消息隊(duì)列)傳遞給從服務(wù),從服務(wù)在接受到消息后進(jìn)行消費(fèi),完成業(yè)務(wù),從而達(dá)到主服務(wù)與從服務(wù)間的消息一致性。首先能想到的也是最簡(jiǎn)單的就是同步事件通知,業(yè)務(wù)處理與消息發(fā)送同步執(zhí)行,實(shí)現(xiàn)邏輯見(jiàn)下方代碼及時(shí)序圖。
public?void?trans()?{??
????try?{??
????//?1.?操作數(shù)據(jù)庫(kù)??
????????bool?result?=?dao.update(data);//?操作數(shù)據(jù)庫(kù)失敗,會(huì)拋出異常??
????//?2.?如果數(shù)據(jù)庫(kù)操作成功則發(fā)送消息??
????????if(result){??
????????????mq.send(data);//?如果方法執(zhí)行失敗,會(huì)拋出異常??
????????}??
????}?catch?(Exception?e)?{??
????????roolback();//?如果發(fā)生異常,就回滾??
????}??
}?

上面的邏輯看上去天衣無(wú)縫,如果數(shù)據(jù)庫(kù)操作失敗則直接退出,不發(fā)送消息;如果發(fā)送消息失敗,則數(shù)據(jù)庫(kù)回滾;如果數(shù)據(jù)庫(kù)操作成功且消息發(fā)送成功,則業(yè)務(wù)成功,消息發(fā)送給下游消費(fèi)。然后仔細(xì)思考后,同步消息通知其實(shí)有兩點(diǎn)不足的地方。
在微服務(wù)的架構(gòu)下,有可能出現(xiàn)網(wǎng)絡(luò)IO問(wèn)題或者服務(wù)器宕機(jī)的問(wèn)題,如果這些問(wèn)題出現(xiàn)在時(shí)序圖的第7步,使得消息投遞后無(wú)法正常通知主服務(wù)(網(wǎng)絡(luò)問(wèn)題),或無(wú)法繼續(xù)提交事務(wù)(宕機(jī)),那么主服務(wù)將會(huì)認(rèn)為消息投遞失敗,會(huì)滾主服務(wù)業(yè)務(wù),然而實(shí)際上消息已經(jīng)被從服務(wù)消費(fèi),那么就會(huì)造成主服務(wù)和從服務(wù)的數(shù)據(jù)不一致。具體場(chǎng)景可見(jiàn)下面兩張時(shí)序圖。


事件服務(wù)(在這里就是消息服務(wù))與業(yè)務(wù)過(guò)于耦合,如果消息服務(wù)不可用,會(huì)導(dǎo)致業(yè)務(wù)不可用。應(yīng)該將事件服務(wù)與業(yè)務(wù)解耦,獨(dú)立出來(lái)異步執(zhí)行,或者在業(yè)務(wù)執(zhí)行后先嘗試發(fā)送一次消息,如果消息發(fā)送失敗,則降級(jí)為異步發(fā)送。
3.1.2 異步事件
3.1.2.1 本地事件服務(wù)
為了解決3.1.1中描述的同步事件的問(wèn)題,異步事件通知模式被發(fā)展了出來(lái),既業(yè)務(wù)服務(wù)和事件服務(wù)解耦,事件異步進(jìn)行,由單獨(dú)的事件服務(wù)保證事件的可靠投遞。

異步事件通知-本地事件服務(wù)
當(dāng)業(yè)務(wù)執(zhí)行時(shí),在同一個(gè)本地事務(wù)中將事件寫(xiě)入本地事件表,同時(shí)投遞該事件,如果事件投遞成功,則將該事件從事件表中刪除。如果投遞失敗,則使用事件服務(wù)定時(shí)地異步統(tǒng)一處理投遞失敗的事件,進(jìn)行重新投遞,直到事件被正確投遞,并將事件從事件表中刪除。這種方式最大可能地保證了事件投遞的實(shí)效性,并且當(dāng)?shù)谝淮瓮哆f失敗后,也能使用異步事件服務(wù)保證事件至少被投遞一次。
然而,這種使用本地事件服務(wù)保證可靠事件通知的方式也有它的不足之處,那便是業(yè)務(wù)仍舊與事件服務(wù)有一定耦合(第一次同步投遞時(shí)),更為嚴(yán)重的是,本地事務(wù)需要負(fù)責(zé)額外的事件表的操作,為數(shù)據(jù)庫(kù)帶來(lái)了壓力,在高并發(fā)的場(chǎng)景,由于每一個(gè)業(yè)務(wù)操作就要產(chǎn)生相應(yīng)的事件表操作,幾乎將數(shù)據(jù)庫(kù)的可用吞吐量砍了一半,這無(wú)疑是無(wú)法接受的。正是因?yàn)檫@樣的原因,可靠事件通知模式進(jìn)一步地發(fā)展-外部事件服務(wù)出現(xiàn)在了人們的眼中。
3.1.2.2 外部事件服務(wù)
外部事件服務(wù)在本地事件服務(wù)的基礎(chǔ)上更進(jìn)了一步,將事件服務(wù)獨(dú)立出主業(yè)務(wù)服務(wù),主業(yè)務(wù)服務(wù)不在對(duì)事件服務(wù)有任何強(qiáng)依賴(lài)。

異步事件通知-外部事件服務(wù)
業(yè)務(wù)服務(wù)在提交前,向事件服務(wù)發(fā)送事件,事件服務(wù)只記錄事件,并不發(fā)送。業(yè)務(wù)服務(wù)在提交或回滾后通知事件服務(wù),事件服務(wù)發(fā)送事件或者刪除事件。不用擔(dān)心業(yè)務(wù)系統(tǒng)在提交或者會(huì)滾后宕機(jī)而無(wú)法發(fā)送確認(rèn)事件給事件服務(wù),因?yàn)槭录?wù)會(huì)定時(shí)獲取所有仍未發(fā)送的事件并且向業(yè)務(wù)系統(tǒng)查詢(xún),根據(jù)業(yè)務(wù)系統(tǒng)的返回來(lái)決定發(fā)送或者刪除該事件。
外部事件雖然能夠?qū)I(yè)務(wù)系統(tǒng)和事件系統(tǒng)解耦,但是也帶來(lái)了額外的工作量:外部事件服務(wù)比起本地事件服務(wù)來(lái)說(shuō)多了兩次網(wǎng)絡(luò)通信開(kāi)銷(xiāo)(提交前、提交/回滾后),同時(shí)也需要業(yè)務(wù)系統(tǒng)提供單獨(dú)的查詢(xún)接口給事件系統(tǒng)用來(lái)判斷未發(fā)送事件的狀態(tài)。
3.1.2.3 可靠事件通知模式的注意事項(xiàng)
可靠事件模式需要注意的有兩點(diǎn),1. 事件的正確發(fā)送;2. 事件的重復(fù)消費(fèi)。
通過(guò)異步消息服務(wù)可以確保事件的正確發(fā)送,然而事件是有可能重復(fù)發(fā)送的,那么就需要消費(fèi)端保證同一條事件不會(huì)重復(fù)被消費(fèi),簡(jiǎn)而言之就是保證事件消費(fèi)的?冪等性
。
如果事件本身是具備冪等性的狀態(tài)型事件,如訂單狀態(tài)的通知(已下單、已支付、已發(fā)貨等),則需要判斷事件的順序。一般通過(guò)時(shí)間戳來(lái)判斷,既消費(fèi)過(guò)了新的消息后,當(dāng)接受到老的消息直接丟棄不予消費(fèi)。如果無(wú)法提供全局時(shí)間戳,則應(yīng)考慮使用全局統(tǒng)一的序列號(hào)。
對(duì)于不具備冪等性的事件,一般是動(dòng)作行為事件,如扣款100,存款200,則應(yīng)該將事件id及事件結(jié)果持久化,在消費(fèi)事件前查詢(xún)事件id,若已經(jīng)消費(fèi)則直接返回執(zhí)行結(jié)果;若是新消息,則執(zhí)行,并存儲(chǔ)執(zhí)行結(jié)果。
3.2 最大努力通知模式
相比可靠事件通知模式,最大努力通知模式就容易理解多了。最大努力通知型的特點(diǎn)是,業(yè)務(wù)服務(wù)在提交事務(wù)后,進(jìn)行有限次數(shù)(設(shè)置最大次數(shù)限制)的消息發(fā)送,比如發(fā)送三次消息,若三次消息發(fā)送都失敗,則不予繼續(xù)發(fā)送。所以有可能導(dǎo)致消息的丟失。
同時(shí),主業(yè)務(wù)方需要提供查詢(xún)接口給從業(yè)務(wù)服務(wù),用來(lái)恢復(fù)丟失消息。最大努力通知型對(duì)于時(shí)效性保證比較差(既可能會(huì)出現(xiàn)較長(zhǎng)時(shí)間的軟狀態(tài)),所以對(duì)于數(shù)據(jù)一致性的時(shí)效性要求比較高的系統(tǒng)無(wú)法使用。這種模式通常使用在不同業(yè)務(wù)平臺(tái)服務(wù)或者對(duì)于第三方業(yè)務(wù)服務(wù)的通知,如銀行通知、商戶(hù)通知等,這里不再展開(kāi)。
3.3 業(yè)務(wù)補(bǔ)償模式
接下來(lái)介紹兩種補(bǔ)償模式,補(bǔ)償模式比起事件通知模式最大的不同是,補(bǔ)償模式的上游服務(wù)依賴(lài)于下游服務(wù)的運(yùn)行結(jié)果,而事件通知模式上游服務(wù)不依賴(lài)于下游服務(wù)的運(yùn)行結(jié)果。首先介紹業(yè)務(wù)補(bǔ)償模式,業(yè)務(wù)補(bǔ)償模式是一種純補(bǔ)償模式,其設(shè)計(jì)理念為,業(yè)務(wù)在調(diào)用的時(shí)候正常提交,當(dāng)一個(gè)服務(wù)失敗的時(shí)候,所有其依賴(lài)的上游服務(wù)都進(jìn)行業(yè)務(wù)補(bǔ)償操作。舉個(gè)例子,小明從杭州出發(fā),去往美國(guó)紐約出差,現(xiàn)在他需要定從杭州去往上海的火車(chē)票,以及從上海飛往紐約的飛機(jī)票。
如果小明成功購(gòu)買(mǎi)了火車(chē)票之后發(fā)現(xiàn)那天的飛機(jī)票已經(jīng)售空了,那么與其在上海再多待一天,小明還不如取消去上海的火車(chē)票,選擇飛往北京再轉(zhuǎn)機(jī)紐約,所以小明就取消了去上海的火車(chē)票。這個(gè)例子中購(gòu)買(mǎi)杭州到上海的火車(chē)票是服務(wù)a,購(gòu)買(mǎi)上海到紐約的飛機(jī)票是服務(wù)b,業(yè)務(wù)補(bǔ)償模式就是在服務(wù)b失敗的時(shí)候,對(duì)服務(wù)a進(jìn)行補(bǔ)償操作,在例子中就是取消杭州到上海的火車(chē)票。
補(bǔ)償模式要求每個(gè)服務(wù)都提供補(bǔ)償借口,且這種補(bǔ)償一般來(lái)說(shuō)是?不完全補(bǔ)償
,既即使進(jìn)行了補(bǔ)償操作,那條取消的火車(chē)票記錄還是一直存在數(shù)據(jù)庫(kù)中可以被追蹤(一般是有相信的狀態(tài)字段“已取消”作為標(biāo)記),畢竟已經(jīng)提交的線(xiàn)上數(shù)據(jù)一般是不能進(jìn)行物理刪除的。
業(yè)務(wù)補(bǔ)償模式最大的缺點(diǎn)是軟狀態(tài)的時(shí)間比較長(zhǎng),既數(shù)據(jù)一致性的時(shí)效性很低,多個(gè)服務(wù)常常可能處于數(shù)據(jù)不一致的情況。
3.4 TCC/Try Confirm Cancel模式
TCC模式是一種優(yōu)化了的業(yè)務(wù)補(bǔ)償模式,它可以做到?完全補(bǔ)償
,既進(jìn)行補(bǔ)償后不留下補(bǔ)償?shù)募o(jì)錄,就好像什么事情都沒(méi)有發(fā)生過(guò)一樣。同時(shí),TCC的軟狀態(tài)時(shí)間很短,原因是因?yàn)門(mén)CC是一種兩階段型模式(已經(jīng)忘了兩階段概念的可以回顧一下1.2.1),只有在所有的服務(wù)的第一階段(try)都成功的時(shí)候才進(jìn)行第二階段確認(rèn)(Confirm)操作,否則進(jìn)行補(bǔ)償(Cancel)操作,而在try階段是不會(huì)進(jìn)行真正的業(yè)務(wù)處理的。

TCC模式
TCC模式的具體流程為兩個(gè)階段:
Try,業(yè)務(wù)服務(wù)完成所有的業(yè)務(wù)檢查,預(yù)留必需的業(yè)務(wù)資源
如果Try在所有服務(wù)中都成功,那么執(zhí)行Confirm操作,Confirm操作不做任何的業(yè)務(wù)檢查(因?yàn)閠ry中已經(jīng)做過(guò)),只是用Try階段預(yù)留的業(yè)務(wù)資源進(jìn)行業(yè)務(wù)處理;否則進(jìn)行Cancel操作,Cancel操作釋放Try階段預(yù)留的業(yè)務(wù)資源。
這么說(shuō)可能比較模糊,下面我舉一個(gè)具體的例子,小明在線(xiàn)從招商銀行轉(zhuǎn)賬100元到廣發(fā)銀行。這個(gè)操作可看作兩個(gè)服務(wù),服務(wù)a從小明的招行賬戶(hù)轉(zhuǎn)出100元,服務(wù)b從小明的廣發(fā)銀行帳戶(hù)匯入100元。
服務(wù)a(小明從招行轉(zhuǎn)出100元):
try:?update?cmb_account?set?balance=balance-100,?freeze=freeze+100?where??
acc_id=1?and?balance>100;??
??
confirm:?update?cmb_account?set?freeze=freeze-100?where?acc_id=1;??
??
cancel:?update?cmb_account?set?balance=balance+100,?freeze=freeze-100?where??
acc_id=1;??
服務(wù)b(小明往廣發(fā)銀行匯入100元):
try:?update?cgb_account?set?freeze=freeze+100?where?acc_id=1;??
??
confirm:?update?cgb_account?set?balance=balance+100,?freeze=freeze-100?where??acc_id=1;??
??
cancel:?update?cgb_account?set?freeze=freeze-100?where?acc_id=1;??
具體說(shuō)明:
a的try階段,服務(wù)做了兩件事,1:業(yè)務(wù)檢查,這里是檢查小明的帳戶(hù)里的錢(qián)是否多余100元;2:預(yù)留資源,將100元從余額中劃入凍結(jié)資金。
a的confirm階段,這里不再進(jìn)行業(yè)務(wù)檢查,因?yàn)閠ry階段已經(jīng)做過(guò)了,同時(shí)由于轉(zhuǎn)賬已經(jīng)成功,將凍結(jié)資金扣除。
a的cancel階段,釋放預(yù)留資源,既100元凍結(jié)資金,并恢復(fù)到余額。
b的try階段進(jìn)行,預(yù)留資源,將100元凍結(jié)。
b的confirm階段,使用try階段預(yù)留的資源,將100元凍結(jié)資金劃入余額。
b的cancel階段,釋放try階段的預(yù)留資源,將100元從凍結(jié)資金中減去。
從上面的簡(jiǎn)單例子可以看出,TCC模式比純業(yè)務(wù)補(bǔ)償模式更加復(fù)雜,所以在實(shí)現(xiàn)上每個(gè)服務(wù)都需要實(shí)現(xiàn)Cofirm和Cancel兩個(gè)接口。
3.5 總結(jié)
下面的表格對(duì)這四種常用的模式進(jìn)行了比較:
| 類(lèi)型 | 名稱(chēng) | 數(shù)據(jù)一致性的實(shí)時(shí)性 | 開(kāi)發(fā)成本 | 上游服務(wù)是否依賴(lài)下游服務(wù)結(jié)果 |
|---|---|---|---|---|
| 通知型 | 最大努力 | 低 | 低 | 不依賴(lài) |
| 通知型 | 可靠事件 | 高 | 高 | 不依賴(lài) |
| 補(bǔ)償型 | 業(yè)務(wù)補(bǔ)償 | 低 | 低 | 依賴(lài) |
| 補(bǔ)償型 | TCC | 高 | 高 | 依賴(lài) |
來(lái)源:jianshu.com/p/b264a196b177
版權(quán)申明:內(nèi)容來(lái)源網(wǎng)絡(luò),版權(quán)歸原創(chuàng)者所有。除非無(wú)法確認(rèn),我們都會(huì)標(biāo)明作者及出處,如有侵權(quán)煩請(qǐng)告知,我們會(huì)立即刪除并表示歉意。謝謝!

