自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一文講透微服務(wù)下如何保證事務(wù)的一致性

開(kāi)發(fā) 架構(gòu)
什么是事務(wù)?回答這個(gè)問(wèn)題之前,我們先來(lái)看一個(gè)經(jīng)典的場(chǎng)景:支付寶等交易平臺(tái)的轉(zhuǎn)賬。假設(shè)小明需要用支付寶給小紅轉(zhuǎn)賬 100000 元,此時(shí),小明帳號(hào)會(huì)少 100000 元,而小紅帳號(hào)會(huì)多 100000 元。

[[311568]]

 從本地事務(wù)到分布式事務(wù)的演變

什么是事務(wù)?回答這個(gè)問(wèn)題之前,我們先來(lái)看一個(gè)經(jīng)典的場(chǎng)景:支付寶等交易平臺(tái)的轉(zhuǎn)賬。假設(shè)小明需要用支付寶給小紅轉(zhuǎn)賬 100000 元,此時(shí),小明帳號(hào)會(huì)少 100000 元,而小紅帳號(hào)會(huì)多 100000 元。如果在轉(zhuǎn)賬過(guò)程中系統(tǒng)崩潰了,小明帳號(hào)少 100000 元,而小紅帳號(hào)金額不變,就會(huì)出大問(wèn)題,因此這個(gè)時(shí)候我們就需要使用事務(wù)了。請(qǐng)參見(jiàn)圖 6-1。

 

這里,體現(xiàn)了事務(wù)一個(gè)很重要的特性:原子性。事實(shí)上,事務(wù)有四個(gè)基本特性:原子性、一致性、隔離性、持久性。其中,原子性,即事務(wù)內(nèi)的操作要么全部成功,要么全部失敗,不會(huì)在中間的某個(gè)環(huán)節(jié)結(jié)束。一致性,即使數(shù)據(jù)庫(kù)在一個(gè)事務(wù)執(zhí)行之前和執(zhí)行之后,數(shù)據(jù)庫(kù)都必須處于一致性狀態(tài)。如果事務(wù)執(zhí)行失敗,那么需要自動(dòng)回滾到原始狀態(tài),換句話說(shuō),事務(wù)一旦提交,其他事務(wù)查看到的結(jié)果一致,事務(wù)一旦回滾,其他事務(wù)也只能看到回滾前的狀態(tài)。隔離性,即在并發(fā)環(huán)境中,不同的事務(wù)同時(shí)修改相同的數(shù)據(jù)時(shí),一個(gè)未完成事務(wù)不會(huì)影響另外一個(gè)未完成事務(wù)。持久性,即事務(wù)一旦提交,其修改的數(shù)據(jù)將永久保存到數(shù)據(jù)庫(kù)中,其改變是永久性的。

本地事務(wù)通過(guò) ACID 保證數(shù)據(jù)的強(qiáng)一致性。ACID是 Atomic(原子性)、Consistency(一致性)、 Isolation(隔離性)和 Durability(持久性)的縮寫 。在實(shí)際開(kāi)發(fā)過(guò)程中,我們或多或少都有使用到本地事務(wù)。例如,MySQL 事務(wù)處理使用到 begin 開(kāi)始一個(gè)事務(wù),rollback 事務(wù)回滾,commit 事務(wù)確認(rèn)。這里,事務(wù)提交后,通過(guò) redo log 記錄變更,通過(guò) undo log 在失敗時(shí)進(jìn)行回滾,保證事務(wù)的原子性。筆者補(bǔ)充下,使用 Java 語(yǔ)言的開(kāi)發(fā)者都接觸過(guò) Spring。Spring 使用 @Transactional 注解就可以搞定事務(wù)功能。事實(shí)上,Spring 封裝了這些細(xì)節(jié),在生成相關(guān)的 Bean 的時(shí)候,在需要注入相關(guān)的帶有 @Transactional 注解的 bean 時(shí)候用代理去注入,在代理中為我們開(kāi)啟提交/回滾事務(wù)。請(qǐng)參見(jiàn)圖6-2。

隨著業(yè)務(wù)的高速發(fā)展,面對(duì)海量數(shù)據(jù),例如,上千萬(wàn)甚至上億的數(shù)據(jù),查詢一次所花費(fèi)的時(shí)間會(huì)變長(zhǎng),甚至?xí)斐蓴?shù)據(jù)庫(kù)的單點(diǎn)壓力。因此,我們就要考慮分庫(kù)與分表方案了。分庫(kù)與分表的目的在于,減小數(shù)據(jù)庫(kù)的單庫(kù)單表負(fù)擔(dān),提高查詢性能,縮短查詢時(shí)間。這里,我們先來(lái)看下單庫(kù)拆分的場(chǎng)景。事實(shí)上,分表策略可以歸納為垂直拆分和水平拆分。垂直拆分,把表的字段進(jìn)行拆分,即一張字段比較多的表拆分為多張表,這樣使得行數(shù)據(jù)變小。一方面,可以減少客戶端程序和數(shù)據(jù)庫(kù)之間的網(wǎng)絡(luò)傳輸?shù)淖止?jié)數(shù),因?yàn)樯a(chǎn)環(huán)境共享同一個(gè)網(wǎng)絡(luò)帶寬,隨著并發(fā)查詢的增多,有可能造成帶寬瓶頸從而造成阻塞。另一方面,一個(gè)數(shù)據(jù)塊能存放更多的數(shù)據(jù),在查詢時(shí)就會(huì)減少 I/O 次數(shù)。水平拆分,把表的行進(jìn)行拆分。因?yàn)楸淼男袛?shù)超過(guò)幾百萬(wàn)行時(shí),就會(huì)變慢,這時(shí)可以把一張的表的數(shù)據(jù)拆成多張表來(lái)存放。水平拆分,有許多策略,例如,取模分表,時(shí)間維度分表等。這種場(chǎng)景下,雖然我們根據(jù)特定規(guī)則分表了,我們?nèi)匀豢梢允褂帽镜厥聞?wù)。但是,庫(kù)內(nèi)分表,僅僅是解決了單表數(shù)據(jù)過(guò)大的問(wèn)題,但并沒(méi)有把單表的數(shù)據(jù)分散到不同的物理機(jī)上,因此并不能減輕 MySQL 服務(wù)器的壓力,仍然存在同一個(gè)物理機(jī)上的資源競(jìng)爭(zhēng)和瓶頸,包括 CPU、內(nèi)存、磁盤 IO、網(wǎng)絡(luò)帶寬等。對(duì)于分庫(kù)拆分的場(chǎng)景,它把一張表的數(shù)據(jù)劃分到不同的數(shù)據(jù)庫(kù),多個(gè)數(shù)據(jù)庫(kù)的表結(jié)構(gòu)一樣。此時(shí),如果我們根據(jù)一定規(guī)則將我們需要使用事務(wù)的數(shù)據(jù)路由到相同的庫(kù)中,可以通過(guò)本地事務(wù)保證其強(qiáng)一致性。但是,對(duì)于按照業(yè)務(wù)和功能劃分的垂直拆分,它將把業(yè)務(wù)數(shù)據(jù)分別放到不同的數(shù)據(jù)庫(kù)中。這里,拆分后的系統(tǒng)就會(huì)遇到數(shù)據(jù)的一致性問(wèn)題,因?yàn)槲覀冃枰ㄟ^(guò)事務(wù)保證的數(shù)據(jù)分散在不同的數(shù)據(jù)庫(kù)中,而每個(gè)數(shù)據(jù)庫(kù)只能保證自己的數(shù)據(jù)可以滿足 ACID 保證強(qiáng)一致性,但是在分布式系統(tǒng)中,它們可能部署在不同的服務(wù)器上,只能通過(guò)網(wǎng)絡(luò)進(jìn)行通信,因此無(wú)法準(zhǔn)確的知道其他數(shù)據(jù)庫(kù)中的事務(wù)執(zhí)行情況。請(qǐng)參見(jiàn)圖6-3。

此外,不僅僅在跨庫(kù)調(diào)用存在本地事務(wù)無(wú)法解決的問(wèn)題,隨著微服務(wù)的落地中,每個(gè)服務(wù)都有自己的數(shù)據(jù)庫(kù),并且數(shù)據(jù)庫(kù)是相互獨(dú)立且透明的。那如果服務(wù) A 需要獲取服務(wù) B 的數(shù)據(jù),就存在跨服務(wù)調(diào)用,如果遇到服務(wù)宕機(jī),或者網(wǎng)絡(luò)連接異常、同步調(diào)用超時(shí)等場(chǎng)景就會(huì)導(dǎo)致數(shù)據(jù)的不一致,這個(gè)也是一種分布式場(chǎng)景下需要考慮數(shù)據(jù)一致性問(wèn)題。請(qǐng)參見(jiàn)圖6-4。

總結(jié)一下,當(dāng)業(yè)務(wù)量級(jí)擴(kuò)大之后的分庫(kù),以及微服務(wù)落地之后的業(yè)務(wù)服務(wù)化,都會(huì)產(chǎn)生分布式數(shù)據(jù)不一致的問(wèn)題。既然本地事務(wù)無(wú)法滿足需求,因此分布式事務(wù)就要登上舞臺(tái)。什么是分布式事務(wù)?我們可以簡(jiǎn)單地理解,它就是為了保證不同數(shù)據(jù)庫(kù)的數(shù)據(jù)一致性的事務(wù)解決方案。這里,我們有必要先來(lái)了解下 CAP 原則和 BASE 理論。CAP 原則是 Consistency(一致性)、Availablity(可用性)和 Partition-tolerance(分區(qū)容錯(cuò)性)的縮寫,它是分布式系統(tǒng)中的平衡理論。在分布式系統(tǒng)中,一致性要求所有節(jié)點(diǎn)每次讀操作都能保證獲取到最新數(shù)據(jù);可用性要求無(wú)論任何故障產(chǎn)生后都能保證服務(wù)仍然可用;分區(qū)容錯(cuò)性要求被分區(qū)的節(jié)點(diǎn)可以正常對(duì)外提供服務(wù)。事實(shí)上,任何系統(tǒng)只可同時(shí)滿足其中二個(gè),無(wú)法三者兼顧。對(duì)于分布式系統(tǒng)而言,分區(qū)容錯(cuò)性是一個(gè)最基本的要求。那么,如果選擇了一致性和分區(qū)容錯(cuò)性,放棄可用性,那么網(wǎng)絡(luò)問(wèn)題會(huì)導(dǎo)致系統(tǒng)不可用。如果選擇可用性和分區(qū)容錯(cuò)性,放棄一致性,不同的節(jié)點(diǎn)之間的數(shù)據(jù)不能及時(shí)同步數(shù)據(jù)而導(dǎo)致數(shù)據(jù)的不一致。請(qǐng)參見(jiàn)圖 6-5。


此時(shí),BASE 理論針對(duì)一致性和可用性提出了一個(gè)方案,BASE 是 Basically Available(基本可用)、Soft-state(軟狀態(tài))和 Eventually Consistent(最終一致性)的縮寫,它是最終一致性的理論支撐。簡(jiǎn)單地理解,在分布式系統(tǒng)中,允許損失部分可用性,并且不同節(jié)點(diǎn)進(jìn)行數(shù)據(jù)同步的過(guò)程存在延時(shí),但是在經(jīng)過(guò)一段時(shí)間的修復(fù)后,最終能夠達(dá)到數(shù)據(jù)的最終一致性。BASE 強(qiáng)調(diào)的是數(shù)據(jù)的最終一致性。相比于 ACID 而言,BASE 通過(guò)允許損失部分一致性來(lái)獲得可用性。

現(xiàn)在,業(yè)內(nèi)比較常用的分布式事務(wù)解決方案,包括強(qiáng)一致性的兩階段提交協(xié)議,三階段提交協(xié)議,以及最終一致性的可靠事件模式、補(bǔ)償模式,阿里的 TCC 模式。我們會(huì)在后面的章節(jié)中詳細(xì)介紹與實(shí)戰(zhàn)。

強(qiáng)一致性解決方案

二階段提交協(xié)議

在分布式系統(tǒng)中,每個(gè)數(shù)據(jù)庫(kù)只能保證自己的數(shù)據(jù)可以滿足 ACID 保證強(qiáng)一致性,但是它們可能部署在不同的服務(wù)器上,只能通過(guò)網(wǎng)絡(luò)進(jìn)行通信,因此無(wú)法準(zhǔn)確的知道其他數(shù)據(jù)庫(kù)中的事務(wù)執(zhí)行情況。因此,為了解決多個(gè)節(jié)點(diǎn)之間的協(xié)調(diào)問(wèn)題,就需要引入一個(gè)協(xié)調(diào)者負(fù)責(zé)控制所有節(jié)點(diǎn)的操作結(jié)果,要么全部成功,要么全部失敗。其中,XA 協(xié)議是一個(gè)分布式事務(wù)協(xié)議,它有兩個(gè)角色:事務(wù)管理者和資源管理者。這里,我們可以把事務(wù)管理者理解為協(xié)調(diào)者,而資源管理者理解為參與者。

XA 協(xié)議通過(guò)二階段提交協(xié)議保證強(qiáng)一致性。

二階段提交協(xié)議,顧名思義,它具有兩個(gè)階段:第一階段準(zhǔn)備,第二階段提交。這里,事務(wù)管理者(協(xié)調(diào)者)主要負(fù)責(zé)控制所有節(jié)點(diǎn)的操作結(jié)果,包括準(zhǔn)備流程和提交流程。第一階段,事務(wù)管理者(協(xié)調(diào)者)向資源管理者(參與者)發(fā)起準(zhǔn)備指令,詢問(wèn)資源管理者(參與者)預(yù)提交是否成功。如果資源管理者(參與者)可以完成,就會(huì)執(zhí)行操作,并不提交,最后給出自己響應(yīng)結(jié)果,是預(yù)提交成功還是預(yù)提交失敗。第二階段,如果全部資源管理者(參與者)都回復(fù)預(yù)提交成功,資源管理者(參與者)正式提交命令。如果其中有一個(gè)資源管理者(參與者)回復(fù)預(yù)提交失敗,則事務(wù)管理者(協(xié)調(diào)者)向所有的資源管理者(參與者)發(fā)起回滾命令。舉個(gè)案例,現(xiàn)在我們有一個(gè)事務(wù)管理者(協(xié)調(diào)者),三個(gè)資源管理者(參與者),那么這個(gè)事務(wù)中我們需要保證這三個(gè)參與者在事務(wù)過(guò)程中的數(shù)據(jù)的強(qiáng)一致性。首先,事務(wù)管理者(協(xié)調(diào)者)發(fā)起準(zhǔn)備指令預(yù)判它們是否已經(jīng)預(yù)提交成功了,如果全部回復(fù)預(yù)提交成功,那么事務(wù)管理者(協(xié)調(diào)者)正式發(fā)起提交命令執(zhí)行數(shù)據(jù)的變更。請(qǐng)參見(jiàn)圖 6-6。

 

 

注意的是,雖然二階段提交協(xié)議為保證強(qiáng)一致性提出了一套解決方案,但是仍然存在一些問(wèn)題。其一,事務(wù)管理者(協(xié)調(diào)者)主要負(fù)責(zé)控制所有節(jié)點(diǎn)的操作結(jié)果,包括準(zhǔn)備流程和提交流程,但是整個(gè)流程是同步的,所以事務(wù)管理者(協(xié)調(diào)者)必須等待每一個(gè)資源管理者(參與者)返回操作結(jié)果后才能進(jìn)行下一步操作。這樣就非常容易造成同步阻塞問(wèn)題。其二,單點(diǎn)故障也是需要認(rèn)真考慮的問(wèn)題。事務(wù)管理者(協(xié)調(diào)者)和資源管理者(參與者)都可能出現(xiàn)宕機(jī),如果資源管理者(參與者)出現(xiàn)故障則無(wú)法響應(yīng)而一直等待,事務(wù)管理者(協(xié)調(diào)者)出現(xiàn)故障則事務(wù)流程就失去了控制者,換句話說(shuō),就是整個(gè)流程會(huì)一直阻塞,甚至極端的情況下,一部分資源管理者(參與者)數(shù)據(jù)執(zhí)行提交,一部分沒(méi)有執(zhí)行提交,也會(huì)出現(xiàn)數(shù)據(jù)不一致性。此時(shí),讀者會(huì)提出疑問(wèn):這些問(wèn)題應(yīng)該都是小概率情況,一般是不會(huì)產(chǎn)生的?是的,但是對(duì)于分布式事務(wù)場(chǎng)景,我們不僅僅需要考慮正常邏輯流程,還需要關(guān)注小概率的異常場(chǎng)景,如果我們對(duì)異常場(chǎng)景缺乏處理方案,可能就會(huì)出現(xiàn)數(shù)據(jù)的不一致性,那么后期靠人工干預(yù)處理,會(huì)是一個(gè)成本非常大的任務(wù),此外,對(duì)于交易的核心鏈路也許就不是數(shù)據(jù)問(wèn)題,而是更加嚴(yán)重的資損問(wèn)題。

三階段提交協(xié)議

二階段提交協(xié)議諸多問(wèn)題,因此三階段提交協(xié)議就要登上舞臺(tái)了。三階段提交協(xié)議是二階段提交協(xié)議的改良版本,它與二階段提交協(xié)議不同之處在于,引入了超時(shí)機(jī)制解決同步阻塞問(wèn)題,此外加入了預(yù)備階段盡可能提早發(fā)現(xiàn)無(wú)法執(zhí)行的資源管理者(參與者)并且終止事務(wù),如果全部資源管理者(參與者)都可以完成,才發(fā)起第二階段的準(zhǔn)備和第三階段的提交。否則,其中任何一個(gè)資源管理者(參與者)回復(fù)執(zhí)行,或者超時(shí)等待,那么就終止事務(wù)??偨Y(jié)一下,三階段提交協(xié)議包括:第一階段預(yù)備,第二階段準(zhǔn)備,第二階段提交。請(qǐng)參見(jiàn)圖 6-7。

 

三階段提交協(xié)議很好的解決了二階段提交協(xié)議帶來(lái)的問(wèn)題,是一個(gè)非常有參考意義的解決方案。但是,極小概率的場(chǎng)景下可能會(huì)出現(xiàn)數(shù)據(jù)的不一致性。因?yàn)槿A段提交協(xié)議引入了超時(shí)機(jī)制,如果出現(xiàn)資源管理者(參與者)超時(shí)場(chǎng)景會(huì)默認(rèn)提交成功,但是如果其沒(méi)有成功執(zhí)行,或者其他資源管理者(參與者)出現(xiàn)回滾,那么就會(huì)出現(xiàn)數(shù)據(jù)的不一致性。

最終一致性解決方案

TCC 模式

二階段提交協(xié)議和三階段提交協(xié)議很好的解決了分布式事務(wù)的問(wèn)題,但是在極端情況下仍然存在數(shù)據(jù)的不一致性,此外它對(duì)系統(tǒng)的開(kāi)銷會(huì)比較大,引入事務(wù)管理者(協(xié)調(diào)者)后,比較容易出現(xiàn)單點(diǎn)瓶頸,以及在業(yè)務(wù)規(guī)模不斷變大的情況下,系統(tǒng)可伸縮性也會(huì)存在問(wèn)題。注意的是,它是同步操作,因此引入事務(wù)后,直到全局事務(wù)結(jié)束才能釋放資源,性能可能是一個(gè)很大的問(wèn)題。因此,在高并發(fā)場(chǎng)景下很少使用。因此,阿里提出了另外一種解決方案:TCC 模式。注意的是,很多讀者把二階段提交等同于二階段提交協(xié)議,這個(gè)是一個(gè)誤區(qū),事實(shí)上,TCC 模式也是一種二階段提交。

TCC 模式將一個(gè)任務(wù)拆分三個(gè)操作:Try、Confirm、Cancel。假如,我們有一個(gè) func() 方法,那么在 TCC 模式中,它就變成了 tryFunc()、confirmFunc()、cancelFunc() 三個(gè)方法。

  1. tryFunc (); 
  2.  
  3. confirmFunc (); 
  4.  
  5. cancelFunc (); 

在 TCC 模式中,主業(yè)務(wù)服務(wù)負(fù)責(zé)發(fā)起流程,而從業(yè)務(wù)服務(wù)提供 TCC 模式的 Try、Confirm、Cancel 三個(gè)操作。其中,還有一個(gè)事務(wù)管理器的角色負(fù)責(zé)控制事務(wù)的一致性。例如,我們現(xiàn)在有三個(gè)業(yè)務(wù)服務(wù):交易服務(wù),庫(kù)存服務(wù),支付服務(wù)。用戶選商品,下訂單,緊接著選擇支付方式進(jìn)行付款,然后這筆請(qǐng)求,交易服務(wù)會(huì)先調(diào)用庫(kù)存服務(wù)扣庫(kù)存,然后交易服務(wù)再調(diào)用支付服務(wù)進(jìn)行相關(guān)的支付操作,然后支付服務(wù)會(huì)請(qǐng)求第三方支付平臺(tái)創(chuàng)建交易并扣款,這里,交易服務(wù)就是主業(yè)務(wù)服務(wù),而庫(kù)存服務(wù)和支付服務(wù)是從業(yè)務(wù)服務(wù)。請(qǐng)參見(jiàn)圖 6-8。

 

我們?cè)賮?lái)梳理下,TCC 模式的流程。第一階段主業(yè)務(wù)服務(wù)調(diào)用全部的從業(yè)務(wù)服務(wù)的 Try 操作,并且事務(wù)管理器記錄操作日志。第二階段,當(dāng)全部從業(yè)務(wù)服務(wù)都成功時(shí),再執(zhí)行 Confirm 操作,否則會(huì)執(zhí)行 Cancel 逆操作進(jìn)行回滾。請(qǐng)參見(jiàn)圖 6-9。

 

現(xiàn)在,我們針對(duì) TCC 模式說(shuō)說(shuō)大致業(yè)務(wù)上的實(shí)現(xiàn)思路。首先,交易服務(wù)(主業(yè)務(wù)服務(wù))會(huì)向事務(wù)管理器注冊(cè)并啟動(dòng)事務(wù)。其實(shí),事務(wù)管理器是一個(gè)概念上的全局事務(wù)管理機(jī)制,可以是一個(gè)內(nèi)嵌于主業(yè)務(wù)服務(wù)的業(yè)務(wù)邏輯,或者抽離出的一個(gè) TCC 框架。事實(shí)上,它會(huì)生成全局事務(wù) ID 用于記錄整個(gè)事務(wù)鏈路,并且實(shí)現(xiàn)了一套嵌套事務(wù)的處理邏輯。當(dāng)主業(yè)務(wù)服務(wù)調(diào)用全部的從業(yè)務(wù)服務(wù)的 try 操作,事務(wù)管理器利用本地事務(wù)記錄相關(guān)事務(wù)日志,這個(gè)案例中,它記錄了調(diào)用庫(kù)存服務(wù)的動(dòng)作記錄,以及調(diào)用支付服務(wù)的動(dòng)作記錄,并將其狀態(tài)設(shè)置成“預(yù)提交”狀態(tài)。這里,調(diào)用從業(yè)務(wù)服務(wù)的 Try 操作就是核心的業(yè)務(wù)代碼。那么, Try 操作怎么和它相對(duì)應(yīng)的 Confirm、Cancel 操作綁定呢?其實(shí),我們可以編寫配置文件建立綁定關(guān)系,或者通過(guò) Spring 的注解添加 confirm 和 cancel 兩個(gè)參數(shù)也是不錯(cuò)的選擇。當(dāng)全部從業(yè)務(wù)服務(wù)都成功時(shí),由事務(wù)管理器通過(guò) TCC 事務(wù)上下文切面執(zhí)行 Confirm 操作,將其狀態(tài)設(shè)置成“成功”狀態(tài),否則執(zhí)行 Cancel 操作將其狀態(tài)設(shè)置成“預(yù)提交”狀態(tài),然后進(jìn)行重試。因此,TCC 模式通過(guò)補(bǔ)償?shù)姆绞奖WC其最終一致性。

TCC 的實(shí)現(xiàn)框架有很多成熟的開(kāi)源項(xiàng)目,例如 tcc-transaction 框架。(關(guān)于 tcc-transaction 框架的細(xì)節(jié),可以閱讀:https://github.com/changmingxie/tcc-transaction)tcc-transaction 框架主要涉及 tcc-transaction-core、tcc-transaction-api、tcc-transaction-spring 三個(gè)模塊。其中,tcc-transaction-core 是 tcc-transaction 的底層實(shí)現(xiàn),tcc-transaction-api 是 tcc-transaction 使用的 API,tcc-transaction-spring 是 tcc-transaction 的 Spring 支持。tcc-transaction 將每個(gè)業(yè)務(wù)操作抽象成事務(wù)參與者,每個(gè)事務(wù)可以包含多個(gè)參與者。參與者需要聲明 try / confirm / cancel 三個(gè)類型的方法。這里,我們通過(guò) @Compensable 注解標(biāo)記在 try 方法上,并定義相應(yīng)的 confirm / cancel 方法。

  1.  // try 方法 
  2.  @Compensable(confirmMethod = "confirmRecord", cancelMethod = "cancelRecord", transactionContextEditor = MethodTransactionContextEditor.class) 
  3.  @Transactional 
  4.  public String record(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {} 
  5.   
  6.  // confirm 方法 
  7.  @Transactional 
  8.  public void confirmRecord(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {} 
  9.    
  10.  // cancel 方法 
  11.  @Transactional 
  12.  public void cancelRecord(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {} 

對(duì)于 tcc-transaction 框架的實(shí)現(xiàn),我們來(lái)了解一些核心思路。tcc-transaction 框架通過(guò) @Compensable 切面進(jìn)行攔截,可以透明化對(duì)參與者 confirm / cancel 方法調(diào)用,從而實(shí)現(xiàn) TCC 模式。這里,tcc-transaction 有兩個(gè)攔截器,請(qǐng)參見(jiàn)圖 6-10。

  • org.mengyun.tcctransaction.interceptor.CompensableTransactionInterceptor,可補(bǔ)償事務(wù)攔截器。
  • org.mengyun.tcctransaction.interceptor.ResourceCoordinatorInterceptor,資源協(xié)調(diào)者攔截器。

這里,需要特別關(guān)注 TransactionContext 事務(wù)上下文,因?yàn)槲覀冃枰h(yuǎn)程調(diào)用服務(wù)的參與者時(shí)通過(guò)參數(shù)的形式傳遞事務(wù)給遠(yuǎn)程參與者。在 tcc-transaction 中,一個(gè)事務(wù) org.mengyun.tcctransaction.Transaction可以有多個(gè)參與者 org.mengyun.tcctransaction.Participant 參與業(yè)務(wù)活動(dòng)。其中,事務(wù)編號(hào) TransactionXid 用于唯一標(biāo)識(shí)一個(gè)事務(wù),它使用 UUID 算法生成,保證唯一性。當(dāng)參與者進(jìn)行遠(yuǎn)程調(diào)用時(shí),遠(yuǎn)程的分支事務(wù)的事務(wù)編號(hào)等于該參與者的事務(wù)編號(hào)。通過(guò)事務(wù)編號(hào)的關(guān)聯(lián) TCC confirm / cancel 方法,使用參與者的事務(wù)編號(hào)和遠(yuǎn)程的分支事務(wù)進(jìn)行關(guān)聯(lián),從而實(shí)現(xiàn)事務(wù)的提交和回滾。事務(wù)狀態(tài) TransactionStatus 包含 :嘗試中狀態(tài) TRYING(1)、確認(rèn)中狀態(tài) CONFIRMING(2)、取消中狀態(tài) CANCELLING(3)。此外,事務(wù)類型 TransactionType 包含 :根事務(wù) ROOT(1)、分支事務(wù) BRANCH(2)。當(dāng)調(diào)用 TransactionManager#begin() 發(fā)起根事務(wù)時(shí),類型為 MethodType.ROOT,并且事務(wù) try 方法被調(diào)用。調(diào)用 TransactionManager#propagationNewBegin() 方法,傳播發(fā)起分支事務(wù)。該方法在調(diào)用方法類型為 MethodType.PROVIDER 并且 事務(wù) try 方法被調(diào)用。調(diào)用 TransactionManager#commit() 方法提交事務(wù)。該方法在事務(wù)處于 confirm / cancel 方法被調(diào)用。類似地,調(diào)用 TransactionManager#rollback() 方法,取消事務(wù)。請(qǐng)參見(jiàn)圖 6-11。

此外,對(duì)于事務(wù)恢復(fù)機(jī)制,tcc-transaction 框架基于 Quartz 實(shí)現(xiàn)調(diào)度,按照一定頻率對(duì)事務(wù)進(jìn)行重試,直到事務(wù)完成或超過(guò)最大重試次數(shù)。如果單個(gè)事務(wù)超過(guò)最大重試次數(shù)時(shí),tcc-transaction 框架不再重試,此時(shí)需要手工介入解決。

這里,我們要特別注意操作的冪等性。冪等機(jī)制的核心是保證資源唯一性,例如重復(fù)提交或服務(wù)端的多次重試只會(huì)產(chǎn)生一份結(jié)果。支付場(chǎng)景、退款場(chǎng)景,涉及金錢的交易不能出現(xiàn)多次扣款等問(wèn)題。事實(shí)上,查詢接口用于獲取資源,因?yàn)樗皇遣樵償?shù)據(jù)而不會(huì)影響到資源的變化,因此不管調(diào)用多少次接口,資源都不會(huì)改變,所以是它是冪等的。而新增接口是非冪等的,因?yàn)檎{(diào)用接口多次,它都將會(huì)產(chǎn)生資源的變化。因此,我們需要在出現(xiàn)重復(fù)提交時(shí)進(jìn)行冪等處理。那么,如何保證冪等機(jī)制呢?事實(shí)上,我們有很多實(shí)現(xiàn)方案。其中,一種方案就是常見(jiàn)的創(chuàng)建唯一索引。在數(shù)據(jù)庫(kù)中針對(duì)我們需要約束的資源字段創(chuàng)建唯一索引,可以防止插入重復(fù)的數(shù)據(jù)。但是,遇到分庫(kù)分表的情況是,唯一索引也就不那么好使了,此時(shí),我們可以先查詢一次數(shù)據(jù)庫(kù),然后判斷是否約束的資源字段存在重復(fù),沒(méi)有的重復(fù)時(shí)再進(jìn)行插入操作。注意的是,為了避免并發(fā)場(chǎng)景,我們可以通過(guò)鎖機(jī)制,例如悲觀鎖與樂(lè)觀鎖保證數(shù)據(jù)的唯一性。這里,分布式鎖是一種經(jīng)常使用的方案,它通常情況下是一種悲觀鎖的實(shí)現(xiàn)。但是,很多人經(jīng)常把悲觀鎖、樂(lè)觀鎖、分布式鎖當(dāng)作冪等機(jī)制的解決方案,這個(gè)是不正確的。除此之外,我們還可以引入狀態(tài)機(jī),通過(guò)狀態(tài)機(jī)進(jìn)行狀態(tài)的約束以及狀態(tài)跳轉(zhuǎn),確保同一個(gè)業(yè)務(wù)的流程化執(zhí)行,從而實(shí)現(xiàn)數(shù)據(jù)冪等。

補(bǔ)償模式

上節(jié),我們提到了重試機(jī)制。事實(shí)上,它也是一種最終一致性的解決方案:我們需要通過(guò)最大努力不斷重試,保證數(shù)據(jù)庫(kù)的操作最終一定可以保證數(shù)據(jù)一致性,如果最終多次重試失敗可以根據(jù)相關(guān)日志并主動(dòng)通知開(kāi)發(fā)人員進(jìn)行手工介入。注意的是,被調(diào)用方需要保證其冪等性。重試機(jī)制可以是同步機(jī)制,例如主業(yè)務(wù)服務(wù)調(diào)用超時(shí)或者非異常的調(diào)用失敗需要及時(shí)重新發(fā)起業(yè)務(wù)調(diào)用。重試機(jī)制可以大致分為固定次數(shù)的重試策略與固定時(shí)間的重試策略。除此之外,我們還可以借助消息隊(duì)列和定時(shí)任務(wù)機(jī)制。消息隊(duì)列的重試機(jī)制,即消息消費(fèi)失敗則進(jìn)行重新投遞,這樣就可以避免消息沒(méi)有被消費(fèi)而被丟棄,例如 RocketMQ 可以默認(rèn)允許每條消息最多重試 16 次,每次重試的間隔時(shí)間可以進(jìn)行設(shè)置。定時(shí)任務(wù)的重試機(jī)制,我們可以創(chuàng)建一張任務(wù)執(zhí)行表,并增加一個(gè)“重試次數(shù)”字段。這種設(shè)計(jì)方案中,我們可以在定時(shí)調(diào)用時(shí),獲取這個(gè)任務(wù)是否是執(zhí)行失敗的狀態(tài)并且沒(méi)有超過(guò)重試次數(shù),如果是則進(jìn)行失敗重試。但是,當(dāng)出現(xiàn)執(zhí)行失敗的狀態(tài)并且超過(guò)重試次數(shù)時(shí),就說(shuō)明這個(gè)任務(wù)永久失敗了,需要開(kāi)發(fā)人員進(jìn)行手工介入與排查問(wèn)題。

除了重試機(jī)制之外,也可以在每次更新的時(shí)候進(jìn)行修復(fù)。例如,對(duì)于社交互動(dòng)的點(diǎn)贊數(shù)、收藏?cái)?shù)、評(píng)論數(shù)等計(jì)數(shù)場(chǎng)景,也許因?yàn)榫W(wǎng)絡(luò)抖動(dòng)或者相關(guān)服務(wù)不可用,導(dǎo)致某段時(shí)間內(nèi)的數(shù)據(jù)不一致,我們就可以在每次更新的時(shí)候進(jìn)行修復(fù),保證系統(tǒng)經(jīng)過(guò)一段較短的時(shí)間的自我恢復(fù)和修正,數(shù)據(jù)最終達(dá)到一致。需要注意的是,使用這種解決方案的情況下,如果某條數(shù)據(jù)出現(xiàn)不一致性,但是又沒(méi)有再次更新修復(fù),那么其永遠(yuǎn)都會(huì)是異常數(shù)據(jù)。

定時(shí)校對(duì)也是一種非常重要的解決手段,它采取周期性的進(jìn)行校驗(yàn)操作來(lái)保證。關(guān)于定時(shí)任務(wù)框架的選型上,業(yè)內(nèi)比較常用的有單機(jī)場(chǎng)景下的 Quartz,以及分布式場(chǎng)景下 Elastic-Job、XXL-JOB、SchedulerX 等分布式定時(shí)任務(wù)中間件。關(guān)于定時(shí)校對(duì)可以分為兩種場(chǎng)景,一種是未完成的定時(shí)重試,例如我們利用定時(shí)任務(wù)掃描還未完成的調(diào)用任務(wù),并通過(guò)補(bǔ)償機(jī)制來(lái)修復(fù),實(shí)現(xiàn)數(shù)據(jù)最終達(dá)到一致。另一種是定時(shí)核對(duì),它需要主業(yè)務(wù)服務(wù)提供相關(guān)查詢接口給從業(yè)務(wù)服務(wù)核對(duì)查詢,用于恢復(fù)丟失的業(yè)務(wù)數(shù)據(jù)?,F(xiàn)在,我們來(lái)試想一下電商場(chǎng)景的退款業(yè)務(wù)。在這個(gè)退款業(yè)務(wù)中會(huì)存在一個(gè)退款基礎(chǔ)服務(wù)和自動(dòng)化退款服務(wù)。此時(shí),自動(dòng)化退款服務(wù)在退款基礎(chǔ)服務(wù)的基礎(chǔ)上實(shí)現(xiàn)退款能力的增強(qiáng),實(shí)現(xiàn)基于多規(guī)則的自動(dòng)化退款,并且通過(guò)消息隊(duì)列接收到退款基礎(chǔ)服務(wù)推送的退款快照信息。但是,由于退款基礎(chǔ)服務(wù)發(fā)送消息丟失或者消息隊(duì)列在多次失敗重試后的主動(dòng)丟棄,都很有可能造成數(shù)據(jù)的不一致性。因此,我們通過(guò)定時(shí)從退款基礎(chǔ)服務(wù)查詢核對(duì),恢復(fù)丟失的業(yè)務(wù)數(shù)據(jù)就顯得特別重要了。

可靠事件模式

在分布式系統(tǒng)中,消息隊(duì)列在服務(wù)端的架構(gòu)中的地位非常重要,主要解決異步處理、系統(tǒng)解耦、流量削峰等場(chǎng)景。多個(gè)系統(tǒng)之間如果同步通信很容易造成阻塞,同時(shí)會(huì)將這些系統(tǒng)會(huì)耦合在一起。因此,引入了消息隊(duì)列,一方面解決了同步通信機(jī)制造成的阻塞,另一方面通過(guò)消息隊(duì)列進(jìn)行業(yè)務(wù)解耦。請(qǐng)參見(jiàn)圖 6-12。

可靠事件模式,通過(guò)引入可靠的消息隊(duì)列,只要保證當(dāng)前的可靠事件投遞并且消息隊(duì)列確保事件傳遞至少一次,那么訂閱這個(gè)事件的消費(fèi)者保證事件能夠在自己的業(yè)務(wù)內(nèi)被消費(fèi)即可。這里,請(qǐng)讀者思考,是否只要引入了消息隊(duì)列就可以解決問(wèn)題了呢?事實(shí)上,只是引入消息隊(duì)列并不能保證其最終的一致性,因?yàn)榉植际讲渴瓠h(huán)境下都是基于網(wǎng)絡(luò)進(jìn)行通信,而網(wǎng)絡(luò)通信過(guò)程中,上下游可能因?yàn)楦鞣N原因而導(dǎo)致消息丟失。

其一,主業(yè)務(wù)服務(wù)發(fā)送消息時(shí)可能因?yàn)橄㈥?duì)列無(wú)法使用而發(fā)生失敗。對(duì)于這種情況,我們可以讓主業(yè)務(wù)服務(wù)(生產(chǎn)者)發(fā)送消息,再進(jìn)行業(yè)務(wù)調(diào)用來(lái)確保。一般的做法是,主業(yè)務(wù)服務(wù)將要發(fā)送的消息持久化到本地?cái)?shù)據(jù)庫(kù),設(shè)置標(biāo)志狀態(tài)為“待發(fā)送”狀態(tài),然后把消息發(fā)送給消息隊(duì)列,消息隊(duì)列收到消息后,也把消息持久化到其存儲(chǔ)服務(wù)中,但并不是立即向從業(yè)務(wù)服務(wù)(消費(fèi)者)投遞消息,而是先向主業(yè)務(wù)服務(wù)(生產(chǎn)者)返回消息隊(duì)列的響應(yīng)結(jié)果,然后主業(yè)務(wù)服務(wù)判斷響應(yīng)結(jié)果執(zhí)行之后的業(yè)務(wù)處理。如果響應(yīng)失敗,則放棄之后的業(yè)務(wù)處理,設(shè)置本地的持久化消息標(biāo)志狀態(tài)為“結(jié)束”狀態(tài)。否則,執(zhí)行后續(xù)的業(yè)務(wù)處理,設(shè)置本地的持久化消息標(biāo)志狀態(tài)為“已發(fā)送”狀態(tài)。

  1. public void doServer (){ 
  2.       
  3. // 發(fā)送消息 
  4.  
  5. send (); 
  6.       
  7. // 執(zhí)行業(yè)務(wù) 
  8.      
  9. exec 
  10. ();  
  11.      
  12. // 更新消息狀態(tài) 
  13.  
  14. updateMsg (); 
  15.  

此外,消息隊(duì)列發(fā)生消息后,也可能從業(yè)務(wù)服務(wù)(消費(fèi)者)宕機(jī)而無(wú)法消費(fèi)。絕大多數(shù)消息中間件對(duì)于這種情況,例如 RabbitMQ、RocketMQ 等引入了 ACK 機(jī)制。注意的是,默認(rèn)的情況下,采用自動(dòng)應(yīng)答,這種方式中消息隊(duì)列會(huì)發(fā)送消息后立即從消息隊(duì)列中刪除該消息。所以,為了確保消息的可靠投遞,我們通過(guò)手動(dòng) ACK 方式,如果從業(yè)務(wù)服務(wù)(消費(fèi)者)因宕機(jī)等原因沒(méi)有發(fā)送 ACK,消息隊(duì)列會(huì)將消息重新發(fā)送,保證消息的可靠性。從業(yè)務(wù)服務(wù)處理完相關(guān)業(yè)務(wù)后通過(guò)手動(dòng) ACK 通知消息隊(duì)列,消息隊(duì)列才從消息隊(duì)列中刪除該持久化消息。那么,消息隊(duì)列如果一直重試失敗而無(wú)法投遞,就會(huì)出現(xiàn)消息主動(dòng)丟棄的情況,我們需要如何解決呢?聰明的讀者可能已經(jīng)發(fā)現(xiàn),我們?cè)谏蟼€(gè)步驟中,主業(yè)務(wù)服務(wù)已經(jīng)將要發(fā)送的消息持久化到本地?cái)?shù)據(jù)庫(kù)。因此,從業(yè)務(wù)服務(wù)消費(fèi)成功后,它也會(huì)向消息隊(duì)列發(fā)送一個(gè)通知消息,此時(shí)它是一個(gè)消息的生產(chǎn)者。主業(yè)務(wù)服務(wù)(消費(fèi)者)接收到消息后,最終把本地的持久化消息標(biāo)志狀態(tài)為“完成”狀態(tài)。說(shuō)到這里,讀者應(yīng)該可以理解到我們使用“正反向消息機(jī)制”確保了消息隊(duì)列可靠事件投遞。當(dāng)然,補(bǔ)償機(jī)制也是必不可少的。定時(shí)任務(wù)會(huì)從數(shù)據(jù)庫(kù)掃描在一定時(shí)間內(nèi)未完成的消息并重新投遞。請(qǐng)參見(jiàn)圖 6-13。

注意的是,因?yàn)閺臉I(yè)務(wù)服務(wù)可能收到消息處理超時(shí)或者服務(wù)宕機(jī),以及網(wǎng)絡(luò)等原因?qū)е露㈥?duì)列收不到消息的處理結(jié)果,因此可靠事件投遞并且消息隊(duì)列確保事件傳遞至少一次。這里,從業(yè)務(wù)服務(wù)(消費(fèi)者)需要保證冪等性。如果從業(yè)務(wù)服務(wù)(消費(fèi)者)沒(méi)有保證接口的冪等性,將會(huì)導(dǎo)致重復(fù)提交等異常場(chǎng)景。此外,我們也可以獨(dú)立消息服務(wù),將消息服務(wù)獨(dú)立部署,根據(jù)不同的業(yè)務(wù)場(chǎng)景共用該消息服務(wù),降低重復(fù)開(kāi)發(fā)服務(wù)的成本。

了解了“可靠事件模式”的方法論后,現(xiàn)在我們來(lái)看一個(gè)真實(shí)的案例來(lái)加深理解。首先,當(dāng)用戶發(fā)起退款后,自動(dòng)化退款服務(wù)會(huì)收到一個(gè)退款的事件消息,此時(shí),如果這筆退款符合自動(dòng)化退款策略的話,自動(dòng)化退款服務(wù)會(huì)先寫入本地?cái)?shù)據(jù)庫(kù)持久化這筆退款快照,緊接著,發(fā)送一條執(zhí)行退款的消息投遞到給消息隊(duì)列,消息隊(duì)列接受到消息后返回響應(yīng)成功結(jié)果,那么自動(dòng)化退款服務(wù)就可以執(zhí)行后續(xù)的業(yè)務(wù)邏輯。與此同時(shí),消息隊(duì)列異步地把消息投遞給退款基礎(chǔ)服務(wù),然后退款基礎(chǔ)服務(wù)執(zhí)行自己業(yè)務(wù)相關(guān)的邏輯,執(zhí)行失敗與否由退款基礎(chǔ)服務(wù)自我保證,如果執(zhí)行成功則發(fā)送一條執(zhí)行退款成功消息投遞到給消息隊(duì)列。最后,定時(shí)任務(wù)會(huì)從數(shù)據(jù)庫(kù)掃描在一定時(shí)間內(nèi)未完成的消息并重新投遞。這里,需要注意的是,自動(dòng)化退款服務(wù)持久化的退款快照可以理解為需要確保投遞成功的消息,由“正反向消息機(jī)制”和“定時(shí)任務(wù)”確保其成功投遞。此外,真正的退款出賬邏輯在退款基礎(chǔ)服務(wù)來(lái)保證,因此它要保證冪等性,及出賬邏輯的收斂。當(dāng)出現(xiàn)執(zhí)行失敗的狀態(tài)并且超過(guò)重試次數(shù)時(shí),就說(shuō)明這個(gè)任務(wù)永久失敗了,需要開(kāi)發(fā)人員進(jìn)行手工介入與排查問(wèn)題。請(qǐng)參見(jiàn)圖 6-14。

總結(jié)一下,引入了消息隊(duì)列并不能保證可靠事件投遞,換句話說(shuō),由于網(wǎng)絡(luò)等各種原因而導(dǎo)致消息丟失不能保證其最終的一致性,因此,我們需要通過(guò)“正反向消息機(jī)制”確保了消息隊(duì)列可靠事件投遞,并且使用補(bǔ)償機(jī)制盡可能在一定時(shí)間內(nèi)未完成的消息并重新投遞。

開(kāi)源項(xiàng)目的分布式事務(wù)實(shí)現(xiàn)解讀

開(kāi)源項(xiàng)目中對(duì)分布式事務(wù)的應(yīng)用有很多值得我們學(xué)習(xí)與借鑒的地方。本節(jié),我們就來(lái)對(duì)其實(shí)現(xiàn)進(jìn)行解讀。

RocketMQ

Apache RocketMQ 是阿里開(kāi)源的一款高性能、高吞吐量的分布式消息中間件。在歷年雙 11 中,RocketMQ 都承擔(dān)了阿里巴巴生產(chǎn)系統(tǒng)全部的消息流轉(zhuǎn),在核心交易鏈路有著穩(wěn)定和出色的表現(xiàn),是承載交易峰值的核心基礎(chǔ)產(chǎn)品之一。RocketMQ 同時(shí)存在商用版 MQ 可在阿里云上購(gòu)買(https://www.aliyun.com/product/ons),阿里巴巴對(duì)于開(kāi)源版本和商業(yè)版本,主要區(qū)別在于:會(huì)開(kāi)源分布式消息所有核心的特性,而在商業(yè)層面,尤其是云平臺(tái)的搭建上面,將運(yùn)維管控、安全授權(quán)、深度培訓(xùn)等納入商業(yè)重中之重。

Apache RocketMQ 4.3 版本正式支持分布式事務(wù)消息。RocketMQ 事務(wù)消息設(shè)計(jì)主要解決了生產(chǎn)者端的消息發(fā)送與本地事務(wù)執(zhí)行的原子性問(wèn)題,換句話說(shuō),如果本地事務(wù)執(zhí)行不成功,則不會(huì)進(jìn)行 MQ 消息推送。那么,聰明的你可能就會(huì)存在疑問(wèn):我們可以先執(zhí)行本地事務(wù),執(zhí)行成功了再發(fā)送 MQ 消息,這樣不就可以保證事務(wù)性的?但是,請(qǐng)你再認(rèn)真的思考下,如果 MQ 消息發(fā)送不成功怎么辦呢?事實(shí)上,RocketMQ 對(duì)此提供一個(gè)很好的思路和解決方案。RocketMQ 首先會(huì)發(fā)送預(yù)執(zhí)行消息到 MQ,并且在發(fā)送預(yù)執(zhí)行消息成功后執(zhí)行本地事務(wù)。緊接著,它根據(jù)本地事務(wù)執(zhí)行結(jié)果進(jìn)行后續(xù)執(zhí)行邏輯,如果本地事務(wù)執(zhí)行結(jié)果是 commit,那么正式投遞 MQ 消息,如果本地事務(wù)執(zhí)行結(jié)果是 rollback,則 MQ 刪除之前投遞的預(yù)執(zhí)行消息,不進(jìn)行投遞下發(fā)。注意的是,對(duì)于異常情況,例如執(zhí)行本地事務(wù)過(guò)程中,服務(wù)器宕機(jī)或者超時(shí),RocketMQ 將會(huì)不停的詢問(wèn)其同組的其他生產(chǎn)者端來(lái)獲取狀態(tài)。請(qǐng)參見(jiàn)圖 6-15。

至此,我們已經(jīng)了解了 RocketMQ 的實(shí)現(xiàn)思路,如果對(duì)源碼實(shí)現(xiàn)感興趣的讀者,可以閱讀

  1. org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl
  2. #sendMessageInTransaction。 

ServiceComb

ServiceComb 基于華為內(nèi)部的 CSE(Cloud Service Engine) 框架開(kāi)源而來(lái),它提供了一套包含代碼框架生成,服務(wù)注冊(cè)發(fā)現(xiàn),負(fù)載均衡,服務(wù)可靠性(容錯(cuò)熔斷,限流降級(jí),調(diào)用鏈追蹤)等功能的微服務(wù)框架。其中,ServiceComb Saga 是一個(gè)微服務(wù)應(yīng)用的數(shù)據(jù)最終一致性解決方案。

Saga 拆分分布式事務(wù)為多個(gè)本地事務(wù),然后由 Saga 引擎負(fù)責(zé)協(xié)調(diào)。如果整個(gè)流程正常結(jié)束,那么業(yè)務(wù)成功完成;如果在這過(guò)程中實(shí)現(xiàn)出現(xiàn)部分失敗,那么Saga 引擎調(diào)用補(bǔ)償操作。Saga 有兩種恢復(fù)的策略 :向前恢復(fù)和向后恢復(fù)。其中,向前恢復(fù)對(duì)失敗的節(jié)點(diǎn)采取最大努力不斷重試,保證數(shù)據(jù)庫(kù)的操作最終一定可以保證數(shù)據(jù)一致性,如果最終多次重試失敗可以根據(jù)相關(guān)日志并主動(dòng)通知開(kāi)發(fā)人員進(jìn)行手工介入。向后恢復(fù)對(duì)之前所有成功的節(jié)點(diǎn)執(zhí)行回滾的事務(wù)操作,這樣保證數(shù)據(jù)達(dá)到一致的效果。

Saga 與 TCC 不同之處在于,Saga 比 TCC 少了一個(gè) Try 操作。因此,Saga 會(huì)直接提交到數(shù)據(jù)庫(kù),然后出現(xiàn)失敗的時(shí)候,進(jìn)行補(bǔ)償操作。Saga 的設(shè)計(jì)可能導(dǎo)致在極端場(chǎng)景下的補(bǔ)償動(dòng)作比較麻煩,但是對(duì)于簡(jiǎn)單的業(yè)務(wù)邏輯侵入性更低,更輕量級(jí),并且減少了通信次數(shù),請(qǐng)參見(jiàn)圖 6-16。

 

ServiceComb Saga 在其理論基礎(chǔ)上進(jìn)行了擴(kuò)展,它包含兩個(gè)組件:alpha 和 omega。alpha 充當(dāng)協(xié)調(diào)者,主要負(fù)責(zé)對(duì)事務(wù)的事件進(jìn)行持久化存儲(chǔ)以及協(xié)調(diào)子事務(wù)的狀態(tài),使其得以最終與全局事務(wù)的狀態(tài)保持一致。omega 是微服務(wù)中內(nèi)嵌的一個(gè) agent,負(fù)責(zé)對(duì)網(wǎng)絡(luò)請(qǐng)求進(jìn)行攔截并向 alpha 上報(bào)事務(wù)事件,并在異常情況下根據(jù) alpha 下發(fā)的指令執(zhí)行相應(yīng)的補(bǔ)償操作。在預(yù)處理階段,alpha 會(huì)記錄事務(wù)開(kāi)始的事件;在后處理階段,alpha 會(huì)記錄事務(wù)結(jié)束的事件。因此,每個(gè)成功的子事務(wù)都有一一對(duì)應(yīng)的開(kāi)始及結(jié)束事件。在服務(wù)生產(chǎn)方,omega 會(huì)攔截請(qǐng)求中事務(wù)相關(guān)的 id 來(lái)提取事務(wù)的上下文。在服務(wù)消費(fèi)方,omega 會(huì)在請(qǐng)求中注入事務(wù)相關(guān)的 id來(lái)傳遞事務(wù)的上下文。通過(guò)服務(wù)提供方和服務(wù)消費(fèi)方的這種協(xié)作處理,子事務(wù)能連接起來(lái)形成一個(gè)完整的全局事務(wù)。注意的是,Saga 要求相關(guān)的子事務(wù)提供事務(wù)處理方法,并且提供補(bǔ)償函數(shù)。這里,添加 @EnableOmega 的注解來(lái)初始化 omega 的配置并與 alpha 建立連接。在全局事務(wù)的起點(diǎn)添加 @SagaStart 的注解,在子事務(wù)添加 @Compensable 的注解指明其對(duì)應(yīng)的補(bǔ)償方法。使用案例:https://github.com/apache/servicecomb-saga/tree/master/saga-demo

  1.  @EnableOmega 
  2.  public class Application{ 
  3.  public static void main(String[] args) { 
  4.       SpringApplication.run(Application.class, args); 
  5.     } 
  6.  } 
  7.    
  8.  @SagaStart 
  9.  public void xxx() { } 
  10.   
  11.   
  12.  @Compensable 
  13.  public void transfer() { } 

現(xiàn)在,我們來(lái)看一下它的業(yè)務(wù)流程圖,請(qǐng)參見(jiàn)圖 6-17。

 

 

責(zé)任編輯:華軒 來(lái)源: 服務(wù)端思維
相關(guān)推薦

2023-11-06 09:06:54

分布式一致性數(shù)據(jù)

2023-10-08 08:29:31

2021-03-04 06:49:53

RocketMQ事務(wù)

2024-04-10 10:34:34

Cache系統(tǒng)GPU

2024-11-07 22:57:30

2022-10-19 12:22:53

并發(fā)扣款一致性

2025-02-10 03:00:00

2022-08-29 08:38:00

事務(wù)一致性

2019-08-30 12:46:10

并發(fā)扣款查詢SQL

2025-03-27 08:20:54

2020-08-05 08:46:10

NFS網(wǎng)絡(luò)文件系統(tǒng)

2024-12-26 15:01:29

2024-01-10 08:01:55

高并發(fā)場(chǎng)景悲觀鎖

2023-09-07 08:11:24

Redis管道機(jī)制

2020-10-28 11:15:24

EPaxos分布式性算法

2023-01-14 17:36:39

微服務(wù)注冊(cè)中心數(shù)據(jù)

2019-09-05 08:43:34

微服務(wù)分布式一致性數(shù)據(jù)共享

2020-07-24 13:54:54

分布式一致性技術(shù)

2020-04-01 15:50:17

TiDBMySQL數(shù)據(jù)庫(kù)

2020-06-01 22:09:48

緩存緩存同步緩存誤用
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)