一、背景
隨著微服務(wù)架構(gòu)的興起,越來越多的公司都對(duì)自身的業(yè)務(wù)架構(gòu)進(jìn)行了微服務(wù)化。在微服務(wù)架構(gòu)中,隨著服務(wù)的逐漸拆分,數(shù)據(jù)庫(kù)的私有化已成為業(yè)界不成文的規(guī)定。因此伴隨著微服務(wù)拆分所帶來的數(shù)據(jù)一致性的問題也愈發(fā)嚴(yán)重,如何解決該問題成為微服務(wù)架構(gòu)落地過程中一個(gè)非常重要的問題。由此我們引出分布式事務(wù)這一概念,用來解決上述背景帶來的問題。在介紹分布式事務(wù)之前先讓我們回顧一下什么是事務(wù)。
二、事務(wù)
事務(wù)是數(shù)據(jù)庫(kù)操作的最小工作單元,是作為單個(gè)邏輯工作單元執(zhí)行的一系列操作;這些操作作為一個(gè)整體一起向系統(tǒng)提交,要么都執(zhí)行、要么都不執(zhí)行;事務(wù)具有ACID四大屬性。
A(Atomic):原子性,構(gòu)成事務(wù)的所有操作,要么都執(zhí)行完成,要么全部不執(zhí)行,不可能出現(xiàn)部分成功部分失敗的情況。
C(Consistency):一致性,在事務(wù)執(zhí)行前后,數(shù)據(jù)庫(kù)的一致性約束沒有被破壞。比如:數(shù)據(jù)庫(kù)約束賬戶余額必須大于0,所以設(shè)置為無符號(hào)數(shù),在此約束下,A只有100元,但要轉(zhuǎn)出200元,此時(shí)數(shù)據(jù)庫(kù)會(huì)保持對(duì)約束的一致性,觸發(fā)執(zhí)行回滾。
I(Isolation):隔離性,數(shù)據(jù)庫(kù)中的事務(wù)一般都是并發(fā)的,隔離性是指并發(fā)的兩個(gè)事務(wù)的執(zhí)行互不干擾,一個(gè)事務(wù)不能看到其他事務(wù)的運(yùn)行過程的中間狀態(tài)。通過配置事務(wù)隔離級(jí)別可以比避免臟讀、重復(fù)讀問題。
D(Durability):持久性,事務(wù)完成之后,該事務(wù)對(duì)數(shù)據(jù)的更改會(huì)持久到數(shù)據(jù)庫(kù),且不會(huì)被回滾。
了解完了事務(wù)的基本概念,接著讓我們看看什么是分布式事務(wù)。
三、分布式事務(wù)
在分布式系統(tǒng)中,一個(gè)應(yīng)用系統(tǒng)拆分為獨(dú)立部署的多個(gè)服務(wù),因此需要服務(wù)與服務(wù)之間遠(yuǎn)程協(xié)作才能完成事務(wù)操作,這種分布式系統(tǒng)環(huán)境下由不同的服務(wù)之間通過網(wǎng)絡(luò)遠(yuǎn)程協(xié)作完成的事務(wù)稱為分布式事務(wù)。
從架構(gòu)角度出發(fā),分布式事務(wù)基本涉及到兩大類。
第一類:一個(gè)事務(wù)請(qǐng)求只涉及單體服務(wù),但是會(huì)操作多張數(shù)據(jù)庫(kù)表,多個(gè)數(shù)據(jù)庫(kù)表操作完成才表示最終完成。
第二類:一個(gè)事務(wù)涉及多個(gè)服務(wù),同時(shí)每個(gè)服務(wù)可能連接著一個(gè)或者多個(gè)數(shù)據(jù)庫(kù),需要協(xié)同多個(gè)獨(dú)立的服務(wù)訪問多個(gè)數(shù)據(jù)存儲(chǔ)最終才能完成。
我們常見的分布式事務(wù)來保證數(shù)據(jù)的一致性的方法分為兩類:強(qiáng)一致性、最終一致性。
采用強(qiáng)一致性的分布式事務(wù)的方案:通常采用兩段式提交協(xié)議2PC、三段式提交協(xié)議3PC。在微服務(wù)架構(gòu)中,該種方式不太適合,原因如下:
由于微服務(wù)間無法直接進(jìn)行數(shù)據(jù)訪問,微服務(wù)間互相調(diào)用通常通過RPC或Http API進(jìn)行,所以已經(jīng)無法使用TM統(tǒng)一管理微服務(wù)的RM
不同的微服務(wù)使用的數(shù)據(jù)源類型可能完全不同,如果微服務(wù)使用了NoSQL之類不原生支持事務(wù)的數(shù)據(jù)庫(kù),業(yè)務(wù)的事務(wù)很難實(shí)現(xiàn)
即使微服務(wù)使用的數(shù)據(jù)源都支持事務(wù),那么如果使用一個(gè)大事務(wù)將許多微服務(wù)的事務(wù)管理起來,這個(gè)大事務(wù)維持的時(shí)間,將比本地事務(wù)長(zhǎng)幾個(gè)數(shù)量級(jí)。如此長(zhǎng)時(shí)間的事務(wù)及跨服務(wù)的事務(wù),將為產(chǎn)生很多鎖及數(shù)據(jù)不可用,嚴(yán)重影響系統(tǒng)性能
因此我們一般采用最終一致性來保證分布式系統(tǒng)的一致性。
常見的最終一致性的分布式事務(wù)解決方案有:事件通知模式(本地異步事件服務(wù)模式、外部事件服務(wù)模式、MQ事務(wù)消息模式、最大努力通知模式)、事務(wù)補(bǔ)償模式(Saga、TCC)。我們通過調(diào)研上述解決方案總結(jié)出了以下特性:

下面我們通過一個(gè)業(yè)務(wù)場(chǎng)景來了解一下什么是分布式事務(wù),并且我們創(chuàng)新行業(yè)是用什么方案來解決數(shù)據(jù)一致性問題的。
四、業(yè)務(wù)場(chǎng)景
假設(shè)有一個(gè)積分簽到系統(tǒng),里面有一個(gè)簽到兌換積分從而兌換物品的功能場(chǎng)景。

我們要做的事情如下:
用戶簽到成功、增加用戶積分
用戶創(chuàng)建兌換物品訂單,訂單狀態(tài)為已支付
扣除用戶積分
扣減物品庫(kù)存
創(chuàng)建物流出庫(kù)單
針對(duì)以上的業(yè)務(wù)場(chǎng)景,我們應(yīng)該如何實(shí)現(xiàn)分布式事務(wù)來滿足業(yè)務(wù)需要。下面主要講事務(wù)補(bǔ)償模式來實(shí)現(xiàn)分布式事務(wù)。
4.1 TCC 模式
TCC是將整體業(yè)務(wù)邏輯的每一個(gè)事務(wù)提交分成了Try,Confirm,Cancel三個(gè)操作。
Try:完成業(yè)務(wù)的準(zhǔn)備工作
Confirm:完成業(yè)務(wù)的提交工作
Cancel:完成業(yè)務(wù)的回滾工作
針對(duì)上述業(yè)務(wù)場(chǎng)景,按照業(yè)務(wù)背景要做的事情,實(shí)現(xiàn)一個(gè)TCC的分布式事務(wù)。
如果要實(shí)現(xiàn)一個(gè)TCC的分布式事務(wù),首先要做的是了解業(yè)務(wù)的主流程以及各個(gè)接口提供的業(yè)務(wù)含義,不直接完成這個(gè)業(yè)務(wù)操作,而是完成一個(gè)Try(預(yù)處理)操作。
例如:給用戶添加積分,我們不直接添加積分,先預(yù)處理添加積分??畚锲穾?kù)存我們也不直接扣庫(kù)存,先凍結(jié)將扣掉的庫(kù)存。具體如下圖

如果Try的邏輯都成功,TCC開始執(zhí)行業(yè)務(wù)的Confirm操作,完成整個(gè)事務(wù)流程。添加用戶積分扣庫(kù)存等操作。如果Try的邏輯部分成功,有部分有問題,那么開始執(zhí)行Cancel操作,撤銷之前執(zhí)行的所有操作。
總體對(duì)于TCC模式來說,要做一個(gè)分布式事務(wù),業(yè)務(wù)中的一個(gè)接口需要完成3個(gè)邏輯的改造,Try-Confirm-Cancel。
服務(wù)調(diào)用鏈路依次執(zhí)行Try邏輯
如果都正常的話,執(zhí)行Confirm邏輯,完成整個(gè)業(yè)務(wù)流程
如果部分服務(wù)的Try邏輯有問題,會(huì)執(zhí)行Cancel邏輯,撤銷之前執(zhí)行的所有操作
TCC模式對(duì)于業(yè)務(wù)的侵入性比較強(qiáng),流程比較繁瑣。各個(gè)業(yè)務(wù)側(cè)都需要支持升級(jí)。對(duì)于我們創(chuàng)新行業(yè)來說,成本有點(diǎn)大,我們選擇了另一種模式來實(shí)現(xiàn)分布式事務(wù)——Saga模式。
4.2 Saga模式
Saga是一種純業(yè)務(wù)補(bǔ)償模式,其設(shè)計(jì)理念為,業(yè)務(wù)在調(diào)用的時(shí)候正常提交,當(dāng)一個(gè)服務(wù)失敗的時(shí)候,所有其依賴的上游服務(wù)都進(jìn)行業(yè)務(wù)補(bǔ)償操作。
Saga的基本概念:
saga:長(zhǎng)事務(wù),long live transaction
每個(gè)本地事務(wù)有對(duì)應(yīng)的補(bǔ)償事務(wù)
執(zhí)行情況
正常:T1 -> T2 -> T3 -> … -> Tn
異常:T1 -> T2 -> T3(異常)-> C3 -> C2 -> C1
Saga兩種恢復(fù)策略:
backward recovery,向后恢復(fù),補(bǔ)償所有已完成的事務(wù)(回滾操作)
forward recovery,向前恢復(fù),重試失敗的事務(wù),假設(shè)每個(gè)子事務(wù)最終都會(huì)成功(重試操作)
Saga事務(wù)的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):模型比TCC更簡(jiǎn)單,只需業(yè)務(wù)方提供事務(wù)執(zhí)行接口transaction、事務(wù)取消補(bǔ)償接口cancel
缺點(diǎn):直接執(zhí)行事務(wù)執(zhí)行接口transaction,可能有副作用(無論是否回滾,都會(huì)執(zhí)行事務(wù)接口的邏輯,舉例:A賬號(hào)向B賬號(hào)轉(zhuǎn)賬,T1事務(wù)對(duì)A用戶扣款,T2事務(wù)對(duì)B用戶加款,T1執(zhí)行成功,同時(shí)產(chǎn)生了一條扣款記錄,T2執(zhí)行失敗需要回滾T2和T1,在這個(gè)過程中的副作用是A賬號(hào)能感知到金額變化和扣款記錄)
針對(duì)上述業(yè)務(wù)背景,我們對(duì)于業(yè)務(wù)側(cè)只需要支持事務(wù)提交的接口( T )和失敗補(bǔ)償?shù)慕涌冢?C )即可。具體流程如下圖:

對(duì)于Saga事務(wù)來說只有完成跟未完成兩種狀態(tài)。無論是事務(wù)全部執(zhí)行成功,還是全部補(bǔ)償成功都視為完成狀態(tài)。出現(xiàn)異常導(dǎo)致流程中斷為未完成狀態(tài)。我們針對(duì)于業(yè)務(wù)提交的流程的Http Code狀態(tài)來區(qū)分是執(zhí)行向前恢復(fù)(重試),還是向后補(bǔ)償(回滾)。對(duì)于沒有補(bǔ)償 C 的業(yè)務(wù),我們將采取向前恢復(fù),直到成功。對(duì)于異常情況,使用離線補(bǔ)償?shù)姆绞綄?duì)未完成的Saga事務(wù)進(jìn)行重做,如長(zhǎng)時(shí)間無法完成將觸發(fā)報(bào)警,人工處理。
我們行創(chuàng)基于上述saga模型研發(fā)了Saga的事務(wù)協(xié)調(diào)器,具體執(zhí)行流程如下:

業(yè)務(wù)方使用需要實(shí)現(xiàn)對(duì)應(yīng)事務(wù)的執(zhí)行方法和補(bǔ)償方法,采用上報(bào)分布式事務(wù)的方式進(jìn)行操作,事務(wù)上報(bào)的數(shù)據(jù)屬性簡(jiǎn)介:
Name:本次流程的名稱
OID:請(qǐng)求流程ID,必須唯一
Process:每個(gè)子流程的屬性簡(jiǎn)介(類型為list,按照順序執(zhí)行)
Transaction:執(zhí)行事務(wù)的流程
Call:服務(wù)調(diào)用的方式,HTTP調(diào)用時(shí)傳GET、POST等
ServiceName:服務(wù)名稱
ServiceMethod:調(diào)用服務(wù)的方法名稱,HTTP傳請(qǐng)求的URL
Body:POST body,string類型
Query:Get Query,map[string]interfase{}
oid:訂單ID, 必傳,必須唯一,用于冪等性校驗(yàn)等
Compensate:執(zhí)行失敗的補(bǔ)償流程
Call:服務(wù)調(diào)用的方式,HTTP調(diào)用時(shí)傳GET、POST等
ServiceName:服務(wù)名稱
ServiceMethod:調(diào)用服務(wù)的方法名稱,HTTP傳請(qǐng)求的URL
Body:POST body,string類型
Query:Get Query,map[string]interfase{}
oid:訂單ID, 必傳,必須唯一,用于冪等性校驗(yàn)等
業(yè)務(wù)方按上述規(guī)則上報(bào)數(shù)據(jù),該事務(wù)會(huì)最終保證數(shù)據(jù)一致性,要么全部成功,要么都失敗回滾。
我們創(chuàng)新行業(yè)采用Saga模型來實(shí)現(xiàn)分布式事務(wù),來解決日常微服務(wù)拆分帶來的數(shù)據(jù)一致性的問題,滿足了我們?nèi)粘5漠a(chǎn)品功能的需要,解決了棘手的問題。
五、總結(jié)
針對(duì)分布式事務(wù)的解決方案都有各自的特點(diǎn),沒有一個(gè)最優(yōu)的方案,需要結(jié)合實(shí)際的應(yīng)用場(chǎng)景選擇合適的模式。
2PC 和 3PC 是一種強(qiáng)一致性事務(wù),不過還是有數(shù)據(jù)不一致,阻塞等風(fēng)險(xiǎn),而且只能用在數(shù)據(jù)庫(kù)層面。
Saga、TCC是一種補(bǔ)償性事務(wù)的思想,對(duì)業(yè)務(wù)入侵較大,需要業(yè)務(wù)方實(shí)現(xiàn)對(duì)應(yīng)的方法。
本地消息、事務(wù)消息和最大努力通知其實(shí)都是最終一致性事務(wù),因此適用于一些對(duì)時(shí)間不敏感的業(yè)務(wù)。