文章摘要:原來大型分布式/微服務(wù)系統(tǒng)中解決數(shù)據(jù)一致性問題,居然是通過……
目前云計(jì)算、大數(shù)據(jù)、互聯(lián)網(wǎng)領(lǐng)域的大部分系統(tǒng)都采用了SOA、微服務(wù)化的架構(gòu)。一個(gè)涉及端到端全鏈路的業(yè)務(wù)操作往往會(huì)由多個(gè)服務(wù)和數(shù)據(jù)庫(kù)實(shí)例共同完成。因此,在一致性要求較高的業(yè)務(wù)場(chǎng)景中,如何保證多個(gè)服務(wù)之間RPC調(diào)用后的數(shù)據(jù)一致將成為關(guān)鍵點(diǎn)。
一、分布式系統(tǒng)/SOA/微服務(wù)架構(gòu)的特點(diǎn):
在大型分布式系統(tǒng)中要同時(shí)能夠滿足,分布式一致性(Consistency)、可用性(Availability)和分區(qū)容忍性(Partitiontolerance),是不存在的。在大多數(shù)情況下只能滿足其中的2項(xiàng),而實(shí)現(xiàn)系統(tǒng)的最終一致性(Base理論)。
(1)CAP特點(diǎn):
a.一致性(Consistency):(同樣數(shù)據(jù)在分布式系統(tǒng)的各個(gè)節(jié)點(diǎn)上都是一致的)
b.可用性(Availability):(所有在分布式系統(tǒng)活躍的節(jié)點(diǎn)都能夠處理操作且能響應(yīng)查詢)
c.分區(qū)容忍性(Partition
Tolerance) :(如果出現(xiàn)了網(wǎng)絡(luò)故障、一部分節(jié)點(diǎn)無法通信,但是系統(tǒng)仍能夠工作)
(2)ACID特點(diǎn):
a.原子性(Atomicity)
一個(gè)事務(wù)(transaction)中的所有操作,要么全部完成,要么全部不完成,不會(huì)結(jié)束在中間某個(gè)環(huán)節(jié)。事務(wù)在執(zhí)行過程中發(fā)生錯(cuò)誤,會(huì)被回滾(Rollback)到事務(wù)開始前的狀態(tài),就像這個(gè)事務(wù)從來沒有執(zhí)行過一樣。
b.一致性(Consistency)
事務(wù)的一致性指的是在一個(gè)事務(wù)執(zhí)行之前和執(zhí)行之后數(shù)據(jù)庫(kù)都必須處于一致性狀態(tài)。如果事務(wù)成功地完成,那么系統(tǒng)中所有變化將正確地應(yīng)用,系統(tǒng)處于有效狀態(tài)。如果在事務(wù)中出現(xiàn)錯(cuò)誤,那么系統(tǒng)中的所有變化將自動(dòng)地回滾,系統(tǒng)返回到原始狀態(tài)。
c.隔離性(Isolation)
指的是在并發(fā)環(huán)境中,當(dāng)不同的事務(wù)同時(shí)操縱相同的數(shù)據(jù)時(shí),每個(gè)事務(wù)都有各自的完整數(shù)據(jù)空間。由并發(fā)事務(wù)所做的修改必須與任何其他并發(fā)事務(wù)所做的修改隔離。事務(wù)查看數(shù)據(jù)更新時(shí),數(shù)據(jù)所處的狀態(tài)要么是另一事務(wù)修改它之前的狀態(tài),要么是另一事務(wù)修改它之后的狀態(tài),事務(wù)不會(huì)查看到中間狀態(tài)的數(shù)據(jù)。
d.持久性(Durability)
指的是只要事務(wù)成功結(jié)束,它對(duì)數(shù)據(jù)庫(kù)所做的更新就必須永久保存下來。即使發(fā)生系統(tǒng)崩潰,重新啟動(dòng)數(shù)據(jù)庫(kù)系統(tǒng)后,數(shù)據(jù)庫(kù)還能恢復(fù)到事務(wù)成功結(jié)束時(shí)的狀態(tài)。
二、分布式事務(wù)的基本介紹
分布式事務(wù)服務(wù)(Distributed
Transaction Service,DTS)是一種分布式事務(wù)框架,用來確保在大規(guī)模分布式/微服務(wù)環(huán)境下端到端業(yè)務(wù)操作的最終一致性。
由CAP定理可知,任何大型的分布式系統(tǒng)/微服務(wù)在一致性、可用性和分區(qū)容忍這三點(diǎn)上只能保證其中的兩點(diǎn)。由于在分布式系統(tǒng)中經(jīng)常發(fā)生丟包、網(wǎng)絡(luò)故障,分區(qū)容忍性是必須要滿足的,同時(shí)為了兼顧高可用性,絕大部分系統(tǒng)都將強(qiáng)一致性需求轉(zhuǎn)化成最終一致性的需求,并通過冪等機(jī)制保證了數(shù)據(jù)的最終一致性。
三、常用的分布式技術(shù)介紹
(1)本地消息表(經(jīng)典的ebay模式)
該方案的核心思想在于分布式系統(tǒng)在處理任務(wù)時(shí)通過消息日志的方式來異步執(zhí)行。消息日志可以存儲(chǔ)至本地文本、數(shù)據(jù)庫(kù)或消息隊(duì)列,然后再通過業(yè)務(wù)規(guī)則定時(shí)任務(wù)或人工自動(dòng)重試。以在線支付系統(tǒng)的跨行轉(zhuǎn)賬為例:
第一步,偽代碼如下,對(duì)用戶id為A的賬戶扣款1000元,通過本地事務(wù)將事務(wù)消息(包括本地事務(wù)id、支付賬戶、收款賬戶、金額、狀態(tài)等)插入至消息表:
Begintransaction
update user_account set amount = amount - 1000where userId = 'A'
insert intotrans_message(xid,payAccount,recAccount,amount,status)values(uuid(),'A','B',1000,1);
endtransaction
commit;
第二步,通知對(duì)方用戶id為B,增加1000元,通常通過消息MQ的方式發(fā)送異步消息,對(duì)方訂閱并監(jiān)聽消息后自動(dòng)觸發(fā)轉(zhuǎn)賬的操作;這里為了保證冪等性,防止觸發(fā)重復(fù)的轉(zhuǎn)賬操作,需要在執(zhí)行轉(zhuǎn)賬操作方新增一個(gè)trans_recv_log表用來做冪等,在第二階段收到消息后,通過判斷trans_recv_log表來檢測(cè)相關(guān)記錄是否被執(zhí)行,如果未被執(zhí)行則會(huì)對(duì)B賬戶余額執(zhí)行加1000元的操作,并會(huì)將該記錄增加至trans_recv_log,事件結(jié)束后通過回調(diào)更新trans_message的狀態(tài)值。
(2)消息中間件
a.非事務(wù)消息中間件
這里仍然以上面跨行轉(zhuǎn)賬為例,我們很難保證在扣款完成之后對(duì)MQ投遞消息的操作就一定能成功。這樣一致性似乎很難保證。以下偽代碼說明了消息投遞的異常:
try{
boolean result = dao.update(model);//更新數(shù)據(jù)庫(kù)失敗拋出異常
if(result){
mq.send(model);//如果MQ超時(shí)或者接收方處理失敗,拋出異常
}
}catch(Exception ex){
rollback();//如果異?;貪L
}
對(duì)于以上的運(yùn)行情況主要有以下幾種:
1.操作數(shù)據(jù)庫(kù)成功,向MQ中投遞消息也成功,該屬于正常情況,一切都OK。
2.操作數(shù)據(jù)庫(kù)失敗,不會(huì)向MQ中投遞消息了。
3.操作數(shù)據(jù)庫(kù)成功,但是向MQ中投遞消息時(shí)失敗,向外拋出了異常,剛剛執(zhí)行的更新數(shù)據(jù)庫(kù)的操作將被回滾。
從上面分析的幾種情況來看,基本上能確保,發(fā)送消息的可靠性。我們?cè)賮矸治鱿孪M(fèi)者端的問題:
1.接收者取出消息后,消費(fèi)者對(duì)應(yīng)的業(yè)務(wù)操作要執(zhí)行成功。如果業(yè)務(wù)執(zhí)行失敗,消息不能失效或者丟失。需要保證消息與業(yè)務(wù)操作一致。
2.盡量確保消息的冪等性。如果出現(xiàn)重復(fù)消息投遞,能夠進(jìn)行冪等而不對(duì)業(yè)務(wù)產(chǎn)生影響。
b.支持事務(wù)的消息中間件
Apache開源的RocketMQ中間件能夠支持一種事務(wù)消息機(jī)制,確保本地操作和發(fā)送消息的異步處理達(dá)到本地事務(wù)的結(jié)果一致。
第一階段,RocketMQ在執(zhí)行本地事務(wù)之前,會(huì)先發(fā)送一個(gè)Prepared消息,并且會(huì)持有這個(gè)消息的接口回查地址。
第二階段,執(zhí)行本地事物操作。
第三階段,確認(rèn)消息發(fā)送,通過第一階段拿到的接口地址URL執(zhí)行回查,并修改狀態(tài),如果本地事務(wù)成功,則修改狀態(tài)為已提交,否則修改狀態(tài)為已回滾。

其中,如果第三階段的確認(rèn)消息發(fā)送失敗后,RocketMQ會(huì)有定時(shí)任務(wù)掃描集群中的事務(wù)消息,如果發(fā)現(xiàn)還是處于prepare狀態(tài)的消息,它會(huì)向消息發(fā)送者確認(rèn)本地事務(wù)是否已執(zhí)行成功。RocketMQ會(huì)根據(jù)發(fā)送端設(shè)置的策略來決定是回滾還是繼續(xù)發(fā)送確認(rèn)消息。這樣就保證了消息的發(fā)送與本地事務(wù)同時(shí)成功或同時(shí)失敗。
再回到上面轉(zhuǎn)賬的例子,如果用戶A的賬戶余額已經(jīng)減少,且消息已經(jīng)發(fā)送成功,作為消費(fèi)者用戶B開始消費(fèi)這條消息,這個(gè)時(shí)候就會(huì)出現(xiàn)消費(fèi)失敗和消費(fèi)超時(shí)兩個(gè)問題,解決超時(shí)問題的思路就是一直重試,直到消費(fèi)端消費(fèi)消息成功,整個(gè)過程中有可能會(huì)出現(xiàn)消息重復(fù)的問題,就需要采用前面說的冪等方案來進(jìn)行處理。
分布式事務(wù)—2PC協(xié)議
為了解決大型分布式/微服務(wù)系統(tǒng)中的一致性問題,業(yè)界比較流行的做法是采用比較著名的有二階提交協(xié)議(2
Phase Commitment Protocol)和三階提交協(xié)議(3
PhaseCommitment Protocol)??紤]到性能問題,三階段提交協(xié)議目前較少被采用。本文也主要介紹二階段協(xié)議。
2PC協(xié)議
二階段提交協(xié)議是分布式系統(tǒng)中較為經(jīng)典的處理數(shù)據(jù)一致性的解決方案。在大型的集群環(huán)境中,對(duì)于單體微服務(wù)本身而言雖然能夠通過代碼質(zhì)量、Mock測(cè)試等方法來確保自身服務(wù)的可用性,但是無法能夠保證其他服務(wù)的可用性。當(dāng)一個(gè)全鏈路的端到端業(yè)務(wù)操作,常常會(huì)跨多個(gè)節(jié)點(diǎn)、多個(gè)應(yīng)用,為了能夠保證全局事務(wù)的ACID特性,需要引入一個(gè)協(xié)調(diào)組件(這里稱之為TM)來控制所有服務(wù)參與者(這里稱之為RM)的操作結(jié)果,根據(jù)所有參與者的反饋結(jié)果來決定整個(gè)分布式事務(wù)究竟是提交還是回滾的結(jié)果。
第一階段:稱為準(zhǔn)備(prepare)階段。事務(wù)協(xié)調(diào)者向各個(gè)服務(wù)應(yīng)用發(fā)送prepare請(qǐng)求,服務(wù)應(yīng)用在得到請(qǐng)求后做預(yù)處理操作,預(yù)處理可能是做預(yù)檢查,也可能是把請(qǐng)求臨時(shí)存儲(chǔ),可以理解為是一種試探性地提交。下面是一般的步驟:
a.事務(wù)協(xié)調(diào)者會(huì)問所有的參與者服務(wù),是否可以提交操作。
b.各個(gè)參與者開始事務(wù)執(zhí)行的準(zhǔn)備工作:如資源上鎖,預(yù)留資源,寫回滾/重試的log。
c.參與者響應(yīng)協(xié)調(diào)者,如果事務(wù)準(zhǔn)備工作成功,則回應(yīng)“可以提交”,否則回應(yīng)拒絕提交。
第二階段:稱為提交(commit)/回滾(rollback)階段。是指事務(wù)真正提交或者回滾的階段。如果事務(wù)協(xié)調(diào)者發(fā)現(xiàn)事務(wù)參與者有一個(gè)在prepare階段出現(xiàn)失敗,則會(huì)要求所有的參與者進(jìn)行回滾。如果協(xié)調(diào)者發(fā)現(xiàn)所有的參與者都prepare操作都是成功,那么他將向所有的參與者發(fā)出提交請(qǐng)求,這時(shí)所有參與者才會(huì)正式提交。由此保證了要求全部提交成功,要么全部失敗。下面是具體步驟:
a.如果所有的參與者都回應(yīng)“可以提交”,那么協(xié)調(diào)者向所有參與者發(fā)送“正式提交”的命令。參與者完成正式提交,并釋放所有資源,然后回應(yīng)“完成”,協(xié)調(diào)者收集各個(gè)服務(wù)的“完成”回應(yīng)后結(jié)束事務(wù)。
b.如果有一個(gè)參與者回應(yīng)“拒絕提交”,那么協(xié)調(diào)者向所有的參與者發(fā)送“回滾操作”,并釋放所有的資源,然后回應(yīng)“回滾完成”,協(xié)調(diào)者收集各個(gè)服務(wù)應(yīng)用的“回滾”返回后,取消整體的分布式事務(wù)。
下圖為二階段的成功和失敗示例圖:

二階段提交協(xié)議解決的是分布式系統(tǒng)/微服務(wù)架構(gòu)中數(shù)據(jù)強(qiáng)一致性的問題,其原理簡(jiǎn)單,但缺點(diǎn)也是存在,主要缺點(diǎn)如下:
a.單點(diǎn)問題:協(xié)調(diào)者在整個(gè)二階段中的作用非常重要,一旦部署協(xié)調(diào)者組件服務(wù)的節(jié)點(diǎn)出現(xiàn)不可用宕機(jī)情況,那么會(huì)影響整個(gè)分布式系統(tǒng)的正常運(yùn)行。
b.同步阻塞:二階段提交執(zhí)行過程中,所有服務(wù)參與者需要服從協(xié)調(diào)者的統(tǒng)一調(diào)度,期間處于阻塞狀態(tài),會(huì)一定程度上影響整個(gè)系統(tǒng)的效率。