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

文章目錄

  1. 從本地事務(wù)到分布式事務(wù)的演變
  2. 強(qiáng)一致性解決方案
    2.1. 二階段提交協(xié)議
    2.2. 三階段提交協(xié)議
  3. 最終一致性解決方案
    3.1. TCC 模式
    3.2. 補(bǔ)償模式
    3.3. 可靠事件模式
  4. 開源項(xiàng)目的分布式事務(wù)實(shí)現(xiàn)解讀
    4.1. RocketMQ
    4.2. ServiceComb

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

什么是事務(wù)哲鸳?回答這個(gè)問題之前梳庆,我們先來看一個(gè)經(jīng)典的場(chǎng)景:支付寶等交易平臺(tái)的轉(zhuǎn)賬邑飒。假設(shè)小明需要用支付寶給小紅轉(zhuǎn)賬 100000 元赴背,此時(shí)驹针,小明帳號(hào)會(huì)少 100000 元吃溅,而小紅帳號(hào)會(huì)多 100000 元风罩。如果在轉(zhuǎn)賬過程中系統(tǒng)崩潰了雄可,小明帳號(hào)少 100000 元扶镀,而小紅帳號(hào)金額不變蕴侣,就會(huì)出大問題,因此這個(gè)時(shí)候我們就需要使用事務(wù)了臭觉。請(qǐng)參見圖 6-1昆雀。

file

http://image.chenyongjun.vip/1531496416779-73bd3182-c8f0-4822-8bde-a5b6cce8ac42.png#width=827

這里,體現(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)聋呢,換句話說苗踪,事務(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ù)通過 ACID 保證數(shù)據(jù)的強(qiáng)一致性饲宿。ACID是 Atomic(原子性)厦酬、Consistency(一致性)胆描、 Isolation(隔離性)和 Durability(持久性)的縮寫 。在實(shí)際開發(fā)過程中仗阅,我們或多或少都有使用到本地事務(wù)昌讲。例如,MySQL 事務(wù)處理使用到 begin 開始一個(gè)事務(wù)减噪,rollback 事務(wù)回滾短绸,commit 事務(wù)確認(rèn)。這里筹裕,事務(wù)提交后醋闭,通過 redo log 記錄變更,通過 undo log 在失敗時(shí)進(jìn)行回滾朝卒,保證事務(wù)的原子性证逻。筆者補(bǔ)充下,使用 Java 語(yǔ)言的開發(fā)者都接觸過 Spring抗斤。Spring 使用 @Transactional 注解就可以搞定事務(wù)功能囚企。事實(shí)上,Spring 封裝了這些細(xì)節(jié)瑞眼,在生成相關(guān)的 Bean 的時(shí)候龙宏,在需要注入相關(guān)的帶有 @Transactional 注解的 bean 時(shí)候用代理去注入,在代理中為我們開啟提交/回滾事務(wù)负拟。請(qǐng)參見圖6-2烦衣。

file

http://image.chenyongjun.vip/1531494414032-a0296a10-8e20-4951-8bc8-54705f954637.png#width=433

隨著業(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í)間部默。這里,我們先來看下單庫(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ù)超過幾百萬(wàn)行時(shí)裆熙,就會(huì)變慢,這時(shí)可以把一張的表的數(shù)據(jù)拆成多張表來存放禽笑。水平拆分入录,有許多策略,例如佳镜,取模分表僚稿,時(shí)間維度分表等。這種場(chǎng)景下蟀伸,雖然我們根據(jù)特定規(guī)則分表了蚀同,我們?nèi)匀豢梢允褂帽镜厥聞?wù)。但是啊掏,庫(kù)內(nèi)分表蠢络,僅僅是解決了單表數(shù)據(jù)過大的問題,但并沒有把單表的數(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ù)中,可以通過本地事務(wù)保證其強(qiáng)一致性。但是晴裹,對(duì)于按照業(yè)務(wù)和功能劃分的垂直拆分,它將把業(yè)務(wù)數(shù)據(jù)分別放到不同的數(shù)據(jù)庫(kù)中涧团。這里,拆分后的系統(tǒng)就會(huì)遇到數(shù)據(jù)的一致性問題钮追,因?yàn)槲覀冃枰ㄟ^事務(wù)保證的數(shù)據(jù)分散在不同的數(shù)據(jù)庫(kù)中,而每個(gè)數(shù)據(jù)庫(kù)只能保證自己的數(shù)據(jù)可以滿足 ACID 保證強(qiáng)一致性阿迈,但是在分布式系統(tǒng)中,它們可能部署在不同的服務(wù)器上刊棕,只能通過網(wǎng)絡(luò)進(jìn)行通信待逞,因此無(wú)法準(zhǔn)確的知道其他數(shù)據(jù)庫(kù)中的事務(wù)執(zhí)行情況识樱。請(qǐng)參見圖6-3。

file

http://image.chenyongjun.vip/1531495019172-4d7f14ad-c739-470a-9116-842f1232be71.png#width=450

此外当犯,不僅僅在跨庫(kù)調(diào)用存在本地事務(wù)無(wú)法解決的問題灶壶,隨著微服務(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ù)一致性問題蚕脏。請(qǐng)參見圖6-4驼鞭。

file

http://image.chenyongjun.vip/1531495271839-57c664d5-ab57-437f-8841-4dd13f592560.png#width=827

總結(jié)一下尺碰,當(dāng)業(yè)務(wù)量級(jí)擴(kuò)大之后的分庫(kù)葱蝗,以及微服務(wù)落地之后的業(yè)務(wù)服務(wù)化,都會(huì)產(chǎn)生分布式數(shù)據(jù)不一致的問題皂甘。既然本地事務(wù)無(wú)法滿足需求偿枕,因此分布式事務(wù)就要登上舞臺(tái)户辫。什么是分布式事務(wù)渔欢?我們可以簡(jiǎn)單地理解,它就是為了保證不同數(shù)據(jù)庫(kù)的數(shù)據(jù)一致性的事務(wù)解決方案苫幢。這里韩肝,我們有必要先來了解下 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ò)問題會(huì)導(dǎo)致系統(tǒng)不可用铸豁。如果選擇可用性和分區(qū)容錯(cuò)性节芥,放棄一致性,不同的節(jié)點(diǎn)之間的數(shù)據(jù)不能及時(shí)同步數(shù)據(jù)而導(dǎo)致數(shù)據(jù)的不一致魄幕。請(qǐng)參見圖 6-5颖杏。

file

http://image.chenyongjun.vip/1531496004484-e10b65da-6c8a-4306-aff4-a6cfa40aaf18.png#width=306

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

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

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

2.1 二階段提交協(xié)議

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

XA 協(xié)議通過二階段提交協(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)備指令逮矛,詢問資源管理者(參與者)預(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ù)過程中的數(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)參見圖 6-6融求。

file

http://image.chenyongjun.vip/1531564854213-8eb8664c-c248-480c-b0d1-f66223b9f6eb.png#width=827

注意的是施掏,雖然二階段提交協(xié)議為保證強(qiáng)一致性提出了一套解決方案七芭,但是仍然存在一些問題。其一预明,事務(wù)管理者(協(xié)調(diào)者)主要負(fù)責(zé)控制所有節(jié)點(diǎn)的操作結(jié)果,包括準(zhǔn)備流程和提交流程阅酪,但是整個(gè)流程是同步的术辐,所以事務(wù)管理者(協(xié)調(diào)者)必須等待每一個(gè)資源管理者(參與者)返回操作結(jié)果后才能進(jìn)行下一步操作施无。這樣就非常容易造成同步阻塞問題猾骡。其二兴想,單點(diǎn)故障也是需要認(rèn)真考慮的問題襟企。事務(wù)管理者(協(xié)調(diào)者)和資源管理者(參與者)都可能出現(xiàn)宕機(jī)顽悼,如果資源管理者(參與者)出現(xiàn)故障則無(wú)法響應(yīng)而一直等待蔚龙,事務(wù)管理者(協(xié)調(diào)者)出現(xiàn)故障則事務(wù)流程就失去了控制者,換句話說,就是整個(gè)流程會(huì)一直阻塞,甚至極端的情況下妖枚,一部分資源管理者(參與者)數(shù)據(jù)執(zhí)行提交绝页,一部分沒有執(zhí)行提交续誉,也會(huì)出現(xiàn)數(shù)據(jù)不一致性酷鸦。此時(shí)井佑,讀者會(huì)提出疑問:這些問題應(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ù)問題拾枣,而是更加嚴(yán)重的資損問題梅肤。

2.2 三階段提交協(xié)議

二階段提交協(xié)議諸多問題姨蝴,因此三階段提交協(xié)議就要登上舞臺(tái)了左医。三階段提交協(xié)議是二階段提交協(xié)議的改良版本跛十,它與二階段提交協(xié)議不同之處在于芥映,引入了超時(shí)機(jī)制解決同步阻塞問題奈偏,此外加入了預(yù)備階段盡可能提早發(fā)現(xiàn)無(wú)法執(zhí)行的資源管理者(參與者)并且終止事務(wù)霎苗,如果全部資源管理者(參與者)都可以完成,才發(fā)起第二階段的準(zhǔn)備和第三階段的提交检眯。否則锰瘸,其中任何一個(gè)資源管理者(參與者)回復(fù)執(zhí)行舞萄,或者超時(shí)等待倒脓,那么就終止事務(wù)崎弃∷亲觯總結(jié)一下盆均,三階段提交協(xié)議包括:第一階段預(yù)備缀踪,第二階段準(zhǔn)備驴娃,第二階段提交。請(qǐng)參見圖 6-7疆柔。

file

http://image.chenyongjun.vip/1531619446532-77825cbd-ddf1-495e-acac-9a12bc34977d.png#width=827

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

3.最終一致性解決方案

3.1 TCC 模式

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

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

tryFunc();
confirmFunc();
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)參見圖 6-8赂蕴。

file

http://image.chenyongjun.vip/1531627307738-ce2acd28-c029-4a42-9a01-76647ac0d4f7.png#width=747

我們?cè)賮硎崂硐铝琓CC 模式的流程。第一階段主業(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)參見圖 6-9放典。

file

http://image.chenyongjun.vip/1531631205284-fd962ae9-2922-42fa-949a-b7061baab7d7.png#width=827

現(xiàn)在碉纺,我們針對(duì) TCC 模式說說大致業(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)系,或者通過 Spring 的注解添加 confirm 和 cancel 兩個(gè)參數(shù)也是不錯(cuò)的選擇为黎。當(dāng)全部從業(yè)務(wù)服務(wù)都成功時(shí)邮丰,由事務(wù)管理器通過 TCC 事務(wù)上下文切面執(zhí)行 Confirm 操作,將其狀態(tài)設(shè)置成“成功”狀態(tài)铭乾,否則執(zhí)行 Cancel 操作將其狀態(tài)設(shè)置成“預(yù)提交”狀態(tài)柠座,然后進(jìn)行重試。因此片橡,TCC 模式通過補(bǔ)償?shù)姆绞奖WC其最終一致性妈经。

TCC 的實(shí)現(xiàn)框架有很多成熟的開源項(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è)類型的方法。這里锋恬,我們通過 @Compensable 注解標(biāo)記在 try 方法上屯换,并定義相應(yīng)的 confirm / cancel 方法。

// try 方法
@Compensable(confirmMethod = "confirmRecord", cancelMethod = "cancelRecord", transactionContextEditor = MethodTransactionContextEditor.class)
@Transactional
public String record(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {}
 
// confirm 方法
@Transactional
public void confirmRecord(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {}
 
// cancel 方法
@Transactional
public void cancelRecord(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {}

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

org.mengyun.tcctransaction.interceptor.CompensableTransactionInterceptor敞斋,可補(bǔ)償事務(wù)攔截器。

org.mengyun.tcctransaction.interceptor.ResourceCoordinatorInterceptor望拖,資源協(xié)調(diào)者攔截器。

file

http://image.chenyongjun.vip/1537093144319-56f5d33f-f3aa-4e92-a7f6-b6f553e03d5d.png#width=827

這里挫鸽,需要特別關(guān)注 TransactionContext 事務(wù)上下文说敏,因?yàn)槲覀冃枰h(yuǎn)程調(diào)用服務(wù)的參與者時(shí)通過參數(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)谴忧。通過事務(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)參見圖 6-11单默。

file

http://image.chenyongjun.vip/1537091911945-2e766c59-5fc3-4b37-bd9c-871b878eb685.png

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

這里粱年,我們要特別注意操作的冪等性售滤。冪等機(jī)制的核心是保證資源唯一性,例如重復(fù)提交或服務(wù)端的多次重試只會(huì)產(chǎn)生一份結(jié)果台诗。支付場(chǎng)景完箩、退款場(chǎng)景讽膏,涉及金錢的交易不能出現(xià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)方案每辟。其中剑辫,一種方案就是常見的創(chuàng)建唯一索引。在數(shù)據(jù)庫(kù)中針對(duì)我們需要約束的資源字段創(chuàng)建唯一索引渠欺,可以防止插入重復(fù)的數(shù)據(jù)妹蔽。但是,遇到分庫(kù)分表的情況是,唯一索引也就不那么好使了胳岂,此時(shí)编整,我們可以先查詢一次數(shù)據(jù)庫(kù),然后判斷是否約束的資源字段存在重復(fù)乳丰,沒有的重復(fù)時(shí)再進(jìn)行插入操作掌测。注意的是,為了避免并發(fā)場(chǎng)景产园,我們可以通過鎖機(jī)制汞斧,例如悲觀鎖與樂觀鎖保證數(shù)據(jù)的唯一性。這里什燕,分布式鎖是一種經(jīng)常使用的方案粘勒,它通常情況下是一種悲觀鎖的實(shí)現(xiàn)。但是屎即,很多人經(jīng)常把悲觀鎖庙睡、樂觀鎖、分布式鎖當(dāng)作冪等機(jī)制的解決方案剑勾,這個(gè)是不正確的埃撵。除此之外赵颅,我們還可以引入狀態(tài)機(jī)虽另,通過狀態(tài)機(jī)進(jìn)行狀態(tài)的約束以及狀態(tài)跳轉(zhuǎn),確保同一個(gè)業(yè)務(wù)的流程化執(zhí)行饺谬,從而實(shí)現(xiàn)數(shù)據(jù)冪等捂刺。

3.2 補(bǔ)償模式

上節(jié),我們提到了重試機(jī)制募寨。事實(shí)上族展,它也是一種最終一致性的解決方案:我們需要通過最大努力不斷重試,保證數(shù)據(jù)庫(kù)的操作最終一定可以保證數(shù)據(jù)一致性拔鹰,如果最終多次重試失敗可以根據(jù)相關(guān)日志并主動(dòng)通知開發(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)行重新投遞片林,這樣就可以避免消息沒有被消費(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)并且沒有超過重試次數(shù)衣盾,如果是則進(jìn)行失敗重試寺旺。但是,當(dāng)出現(xiàn)執(zhí)行失敗的狀態(tài)并且超過重試次數(shù)時(shí)势决,就說明這個(gè)任務(wù)永久失敗了阻塑,需要開發(fā)人員進(jì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)過一段較短的時(shí)間的自我恢復(fù)和修正车酣,數(shù)據(jù)最終達(dá)到一致曲稼。需要注意的是,使用這種解決方案的情況下湖员,如果某條數(shù)據(jù)出現(xiàn)不一致性贫悄,但是又沒有再次更新修復(fù),那么其永遠(yuǎn)都會(huì)是異常數(shù)據(jù)娘摔。

定時(shí)校對(duì)也是一種非常重要的解決手段窄坦,它采取周期性的進(jìn)行校驗(yàn)操作來保證。關(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ù)父泳,并通過補(bǔ)償機(jī)制來修復(fù)般哼,實(shí)現(xiàn)數(shù)據(jù)最終達(dá)到一致。另一種是定時(shí)核對(duì)惠窄,它需要主業(yè)務(wù)服務(wù)提供相關(guān)查詢接口給從業(yè)務(wù)服務(wù)核對(duì)查詢蒸眠,用于恢復(fù)丟失的業(yè)務(wù)數(shù)據(jù)。現(xiàn)在杆融,我們來試想一下電商場(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)化退款,并且通過消息隊(duì)列接收到退款基礎(chǔ)服務(wù)推送的退款快照信息藕各。但是池摧,由于退款基礎(chǔ)服務(wù)發(fā)送消息丟失或者消息隊(duì)列在多次失敗重試后的主動(dòng)丟棄,都很有可能造成數(shù)據(jù)的不一致性激况。因此作彤,我們通過定時(shí)從退款基礎(chǔ)服務(wù)查詢核對(duì),恢復(fù)丟失的業(yè)務(wù)數(shù)據(jù)就顯得特別重要了乌逐。

3.3 可靠事件模式

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

file

http://image.chenyongjun.vip/1531650602775-5306d70b-f314-4c78-939f-f5e906c203bf.png#width=518

可靠事件模式,通過引入可靠的消息隊(duì)列够话,只要保證當(dāng)前的可靠事件投遞并且消息隊(duì)列確保事件傳遞至少一次蓝翰,那么訂閱這個(gè)事件的消費(fèi)者保證事件能夠在自己的業(yè)務(wù)內(nèi)被消費(fèi)即可。這里女嘲,請(qǐng)讀者思考畜份,是否只要引入了消息隊(duì)列就可以解決問題了呢?事實(shí)上欣尼,只是引入消息隊(duì)列并不能保證其最終的一致性爆雹,因?yàn)榉植际讲渴瓠h(huán)境下都是基于網(wǎng)絡(luò)進(jìn)行通信停蕉,而網(wǎng)絡(luò)通信過程中,上下游可能因?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)用來確保。一般的做法是驻子,主業(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)。

public void doServer(){
    // 發(fā)送消息
    send();
    // 執(zhí)行業(yè)務(wù)
    exec();
    // 更新消息狀態(tài)
    updateMsg();
}

此外座云,消息隊(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ì)列中刪除該消息睬隶。所以锣夹,為了確保消息的可靠投遞,我們通過手動(dòng) ACK 方式苏潜,如果從業(yè)務(wù)服務(wù)(消費(fèi)者)因宕機(jī)等原因沒有發(fā)送 ACK银萍,消息隊(duì)列會(huì)將消息重新發(fā)送,保證消息的可靠性恤左。從業(yè)務(wù)服務(wù)處理完相關(guān)業(yè)務(wù)后通過手動(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)。說到這里油吭,讀者應(yīng)該可以理解到我們使用“正反向消息機(jī)制”確保了消息隊(duì)列可靠事件投遞击蹲。當(dāng)然,補(bǔ)償機(jī)制也是必不可少的婉宰。定時(shí)任務(wù)會(huì)從數(shù)據(jù)庫(kù)掃描在一定時(shí)間內(nèi)未完成的消息并重新投遞歌豺。請(qǐng)參見圖 6-13。

file

http://image.chenyongjun.vip/1531653576174-68f9878c-8597-4e0a-a939-f3779b2d513b.png#width=548

注意的是心包,因?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)者)沒有保證接口的冪等性娃殖,將會(huì)導(dǎo)致重復(fù)提交等異常場(chǎng)景值戳。此外,我們也可以獨(dú)立消息服務(wù)珊随,將消息服務(wù)獨(dú)立部署述寡,根據(jù)不同的業(yè)務(wù)場(chǎng)景共用該消息服務(wù)柿隙,降低重復(fù)開發(fā)服務(wù)的成本叶洞。

了解了“可靠事件模式”的方法論后,現(xiàn)在我們來看一個(gè)真實(shí)的案例來加深理解禀崖。首先衩辟,當(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ù)來保證潭兽,因此它要保證冪等性,及出賬邏輯的收斂斗遏。當(dāng)出現(xiàn)執(zhí)行失敗的狀態(tài)并且超過重試次數(shù)時(shí)山卦,就說明這個(gè)任務(wù)永久失敗了,需要開發(fā)人員進(jìn)行手工介入與排查問題诵次。請(qǐng)參見圖 6-14账蓉。

file

http://image.chenyongjun.vip/1532831992334-082988a3-2afe-432f-90de-9f524ddddd4f.jpeg#width=827

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

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

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

4.1 RocketMQ

Apache RocketMQ 是阿里開源的一款高性能壳坪、高吞吐量的分布式消息中間件舶得。在歷年雙 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ì)于開源版本和商業(yè)版本九孩,主要區(qū)別在于:會(huì)開源分布式消息所有核心的特性先馆,而在商業(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ù)執(zhí)行不成功设预,則不會(huì)進(jìn)行 MQ 消息推送。那么犁河,聰明的你可能就會(huì)存在疑問:我們可以先執(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ù)過程中,服務(wù)器宕機(jī)或者超時(shí)窃肠,RocketMQ 將會(huì)不停的詢問其同組的其他生產(chǎn)者端來獲取狀態(tài)包个。請(qǐng)參見圖 6-15。

file

http://image.chenyongjun.vip/1543055101485-f502ebd5-2e09-4741-89bc-d2a7f9c7954f.png#width=827

至此冤留,我們已經(jīng)了解了 RocketMQ 的實(shí)現(xiàn)思路碧囊,如果對(duì)源碼實(shí)現(xiàn)感興趣的讀者,可以閱讀org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendMessageInTransaction纤怒。

4.2 ServiceComb

ServiceComb 基于華為內(nèi)部的 CSE(Cloud Service Engine) 框架開源而來糯而,它提供了一套包含代碼框架生成,服務(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ù)成功完成龟劲;如果在這過程中實(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)通知開發(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)參見圖 6-16挤渐。

file

http://image.chenyongjun.vip/1543062519407-307e6128-dcf8-45ac-b132-210c369a1cc7.png#width=827

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ù)開始的事件漓骚;在后處理階段蝌衔,alpha 會(huì)記錄事務(wù)結(jié)束的事件。因此蝌蹂,每個(gè)成功的子事務(wù)都有一一對(duì)應(yīng)的開始及結(jié)束事件噩斟。在服務(wù)生產(chǎn)方,omega 會(huì)攔截請(qǐng)求中事務(wù)相關(guān)的 id 來提取事務(wù)的上下文孤个。在服務(wù)消費(fèi)方剃允,omega 會(huì)在請(qǐng)求中注入事務(wù)相關(guān)的 id來傳遞事務(wù)的上下文。通過服務(wù)提供方和服務(wù)消費(fèi)方的這種協(xié)作處理硼身,子事務(wù)能連接起來形成一個(gè)完整的全局事務(wù)硅急。注意的是,Saga 要求相關(guān)的子事務(wù)提供事務(wù)處理方法佳遂,并且提供補(bǔ)償函數(shù)营袜。這里,添加 @EnableOmega 的注解來初始化 omega 的配置并與 alpha 建立連接丑罪。在全局事務(wù)的起點(diǎn)添加 @SagaStart 的注解荚板,在子事務(wù)添加 @Compensable 的注解指明其對(duì)應(yīng)的補(bǔ)償方法。
使用案例:https://github.com/apache/servicecomb-saga/tree/master/saga-demo

@EnableOmega
public class Application{
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}
 
@SagaStart
public void xxx() { }
 
 
@Compensable
public void transfer() { }

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

file

http://image.chenyongjun.vip/1543063363079-27d49deb-37d9-4529-8b84-c5ededfcdc8f.png#width=827

轉(zhuǎn)自:http://blog.720ui.com/2020/microservice_transaction/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末煤搜,一起剝皮案震驚了整個(gè)濱河市免绿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌擦盾,老刑警劉巖嘲驾,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異迹卢,居然都是意外死亡辽故,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門腐碱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來誊垢,“玉大人,你說我怎么就攤上這事∥棺撸” “怎么了殃饿?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)缴啡。 經(jīng)常有香客問我壁晒,道長(zhǎng)瓷们,這世上最難降的妖魔是什么业栅? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮谬晕,結(jié)果婚禮上碘裕,老公的妹妹穿的比我還像新娘。我一直安慰自己攒钳,他們只是感情好帮孔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著不撑,像睡著了一般文兢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上焕檬,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天姆坚,我揣著相機(jī)與錄音,去河邊找鬼实愚。 笑死兼呵,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的腊敲。 我是一名探鬼主播击喂,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼碰辅!你這毒婦竟也來了懂昂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤没宾,失蹤者是張志新(化名)和其女友劉穎凌彬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榕吼,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饿序,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了羹蚣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片原探。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖转培,靈堂內(nèi)的尸體忽然破棺而出绑蔫,到底是詐尸還是另有隱情,我是刑警寧澤沟启,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布型型,位于F島的核電站段审,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏闹蒜。R本人自食惡果不足惜寺枉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绷落。 院中可真熱鬧姥闪,春花似錦、人聲如沸砌烁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)函喉。三九已至避归,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間管呵,已是汗流浹背梳毙。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撇寞,地道東北人顿天。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蔑担,于是被迫代替她去往敵國(guó)和親牌废。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容