架構(gòu)師必備的那些分布式事務(wù)解決方案!!

為了保證分布式事務(wù)的正確性,目前互聯(lián)網(wǎng)領(lǐng)域有幾種流行的解決方案,但是大部分都沒(méi)有像XA事務(wù)一樣形成標(biāo)準(zhǔn)的工業(yè)規(guī)范。但是這些方案在某些特定的行業(yè)或者業(yè)務(wù)場(chǎng)景下卻得到了越來(lái)越多的開(kāi)發(fā)者的認(rèn)可。

避免分布式事務(wù)

此方案提倡盡量避免分布式事務(wù),不僅僅是因?yàn)榉植际绞聞?wù)的難度,更是因?yàn)閷?shí)現(xiàn)分布式事務(wù)需要更多的高級(jí)人才。如果一個(gè)操作設(shè)計(jì)到事務(wù)操作,而這些事務(wù)操作可以利用單機(jī)事務(wù)來(lái)解決,推薦首選單機(jī)事務(wù)。

當(dāng)然,是否可以避免分布式事務(wù)還要看具體業(yè)務(wù),在微服務(wù)盛行的當(dāng)下,更多的還要看領(lǐng)域的劃分標(biāo)準(zhǔn),如果兩個(gè)微服務(wù)可以合并成一個(gè)微服務(wù),一定程度上在領(lǐng)域劃分標(biāo)準(zhǔn)接受范圍之內(nèi),可以考慮利用合并的方式來(lái)避免分布式服務(wù)。

舉一個(gè)很簡(jiǎn)單的栗子:一個(gè)用戶基本信息服務(wù)和用戶資產(chǎn)服務(wù)(比如:用戶經(jīng)驗(yàn)值),當(dāng)用戶修改資料的時(shí)候給用戶加貢獻(xiàn)值這個(gè)業(yè)務(wù)場(chǎng)景下,因?yàn)樯婕暗接脩糍Y料修改和加貢獻(xiàn)值兩個(gè)不同服務(wù)的操作,這個(gè)時(shí)候就可以考慮將兩個(gè)服務(wù)合并為一個(gè)服務(wù),用單機(jī)的數(shù)據(jù)庫(kù)事務(wù)來(lái)代替分布式事務(wù)。

在可以避免分布式事務(wù)的情況下,首選避免分布式事務(wù)

二階段提交

二階段(2PC)提交方案是基于X/OpenDTP標(biāo)準(zhǔn)規(guī)范的,最大的缺點(diǎn)在于它在第一階段需要鎖定資源,會(huì)大大降低系統(tǒng)的性能,大型的互聯(lián)網(wǎng)應(yīng)用并不推薦這種方案,那種對(duì)性能不敏感的企業(yè)級(jí)應(yīng)用可以嘗試使用。

在asp.net中,微軟已經(jīng)提供了分布式事務(wù)的管理類型:TransactionScope,它依賴DTC(Distributed Transaction Coordinator)服務(wù)完成事務(wù)一致性。當(dāng)它包裹的代碼中如果設(shè)計(jì)到多個(gè)不同物理位置的數(shù)據(jù)庫(kù)的時(shí)候,它會(huì)自動(dòng)升級(jí)為分布式事務(wù),使用起來(lái)非常方便。

using (TransactionScope ts = new TransactionScope())
            {
                數(shù)據(jù)庫(kù)A操作();
                數(shù)據(jù)庫(kù)B操作();
                數(shù)據(jù)庫(kù)C操作();
                ts.Complete();
            }

點(diǎn)贊關(guān)注?。〖尤胛覀?,了解更多。642830685。群內(nèi)免費(fèi)領(lǐng)取最新軟件測(cè)試大廠面試資料和Python自動(dòng)化、接口、框架搭建學(xué)習(xí)資料!技術(shù)大牛解惑答疑,同行一起交流。

TCC

TCC本質(zhì)上是一種編程模型,它提倡的是補(bǔ)償操作,所以一般情況下它會(huì)有重試機(jī)制,它約定參與事務(wù)的每個(gè)業(yè)務(wù)方都需要提供三個(gè)接口,具體情況請(qǐng)查看上一篇文章。由于TCC的接口重試特性,所以提供的提交和取消接口必須實(shí)現(xiàn)冪等性。

2PC主要是針對(duì)數(shù)據(jù)庫(kù)操作,而TCC主要是針對(duì)業(yè)務(wù)層面來(lái)進(jìn)行操作,這在性能上比2PC要高很多,例如一個(gè)提交訂單的場(chǎng)景,商品服務(wù)需要扣除庫(kù)存,而訂單系統(tǒng)需要?jiǎng)?chuàng)建訂單,代碼類似以下,請(qǐng)不要糾結(jié)命名和參數(shù):

//訂單服務(wù)
public interface IOrderService
{
     //創(chuàng)建一個(gè)不可見(jiàn)的訂單,返回訂單號(hào)
    Task<string> CreateOrder();
    //根據(jù)訂單號(hào)提交訂單,使訂單可見(jiàn)
    Task<int> SubmitOrder(string orderNo);
    //根據(jù)訂單號(hào)取消訂單
    Task<int> CancleOrder(string orderNo);
} 
//商品服務(wù)
public interface IProductService
{
    //根據(jù)商品id,鎖定庫(kù)存,返回鎖定的id
    Task<int> LockProductStock(int productId);
    //根據(jù)鎖定的庫(kù)存id,提交事務(wù),扣除商品庫(kù)存
    Task<int> SubmitLockStock(int lockId);
    //根據(jù)鎖定的庫(kù)存id,取消事務(wù),商品庫(kù)存回滾
    Task<int> CancleLockStock(int lockId);
}

其實(shí)TCC實(shí)現(xiàn)過(guò)程中,還有很多細(xì)節(jié)。比如:當(dāng)提交事務(wù)階段,有一個(gè)節(jié)點(diǎn)由于網(wǎng)絡(luò)原因或者down機(jī)提交失敗,該怎么辦呢?這個(gè)時(shí)候我們要在本地引入本地消息機(jī)制,或者叫做業(yè)務(wù)活動(dòng)管理器,把每個(gè)業(yè)務(wù)參與分布式事務(wù)的每個(gè)操作都記錄下來(lái),當(dāng)某個(gè)過(guò)程的某個(gè)節(jié)點(diǎn)操作失敗,無(wú)論是自動(dòng)發(fā)起重試,還是手動(dòng)重試都可以達(dá)到最終數(shù)據(jù)的一致性。


基于消息的事務(wù)

基于消息的分布式事務(wù)實(shí)現(xiàn)的是最終一致性,它是基于BASE理論的一個(gè)解決方案,最早由eBay提出并實(shí)施,它采用了消息隊(duì)列來(lái)輔助實(shí)現(xiàn)事務(wù)控制流程,核心思想是將需要分布式處理的任務(wù)通過(guò)MQ分發(fā)給每個(gè)業(yè)務(wù)去異步執(zhí)行,如果任務(wù)失敗,則可以發(fā)起系統(tǒng)自動(dòng)重試或者人工重試的糾正流程。

還是以上邊的創(chuàng)建訂單和扣減庫(kù)存為栗子:

首先調(diào)用訂單服務(wù)的創(chuàng)建訂單接口創(chuàng)建訂單,如果創(chuàng)建成功,則發(fā)送需要扣減庫(kù)存的消息(也可以看做創(chuàng)建訂單成功的消息)到MQ。
商品服務(wù)監(jiān)聽(tīng)扣減庫(kù)存消息隊(duì)列,如果收到扣減庫(kù)存消息,則執(zhí)行扣減庫(kù)存操作,如果操作成功,則回復(fù)MQ刪除該消息。如果沒(méi)有操作成功,則準(zhǔn)備接收同樣消息的下次投遞。


這個(gè)流程看似很完美,其實(shí)有很多漏洞。

創(chuàng)建訂單是第一步操作,可以看做是單純的單機(jī)操作,這個(gè)并沒(méi)有問(wèn)題,但是接著發(fā)送MQ消息這一步需要和創(chuàng)建訂單保證事務(wù)性,因?yàn)闀?huì)發(fā)生創(chuàng)建訂單成功,發(fā)送mq消息失敗的情況。如果不能用技術(shù)手段來(lái)保證這兩步的事務(wù),也可以采用引入本地消息的方案,在創(chuàng)建訂單的時(shí)候,用訂單數(shù)據(jù)庫(kù)來(lái)保證訂單創(chuàng)建成功和創(chuàng)建訂單消息表的一致性。然后發(fā)送mq成功之后,修改訂單消息表的狀態(tài)為發(fā)送成功,如果發(fā)送mq消息失敗,則啟用另外一個(gè)線程或者進(jìn)程進(jìn)行重試。
商品服務(wù)扣減庫(kù)存類似,扣減庫(kù)存這個(gè)操作和回復(fù)mq消息這兩個(gè)操作也可以利用本地消息表的方式來(lái)解決一致性問(wèn)題。當(dāng)收到扣減庫(kù)存消息的時(shí)候,扣減庫(kù)存和添加消息成功處理記錄可以利用數(shù)據(jù)庫(kù)的事務(wù)來(lái)保證一致性,如果回復(fù)消息隊(duì)列ack失敗,就算是有重復(fù)消息,也可以根據(jù)本地的消費(fèi)消息表來(lái)過(guò)濾重復(fù)消息
基于消息的分布式解決方案還有一個(gè)劣勢(shì),如果一個(gè)事務(wù)的業(yè)務(wù)參與方非常多,消息的發(fā)送可能會(huì)非常復(fù)雜,需要非常謹(jǐn)慎的設(shè)計(jì)。比如以上訂單的栗子,現(xiàn)在引入了優(yōu)惠券服務(wù),在訂單創(chuàng)建成功,需要同時(shí)扣減庫(kù)存和優(yōu)惠券,如果優(yōu)惠券扣減失敗,需要同時(shí)回滾庫(kù)存和取消訂單,這也只是三個(gè)業(yè)務(wù)參與方,如果是四個(gè),五個(gè)呢?當(dāng)然這在業(yè)務(wù)中也許并不常見(jiàn)。

基于消息的分布式事務(wù)解決方案,由于引入了重試機(jī)制,也需要接口在實(shí)現(xiàn)的時(shí)候支持冪等性。但從開(kāi)發(fā)的角度,這種方案要比tcc以及2pc都要有優(yōu)勢(shì),把每個(gè)系統(tǒng)之間的耦合度降到了最低,而且每個(gè)業(yè)務(wù)方的實(shí)現(xiàn)技術(shù)可以非常靈活,無(wú)論是采用java還是c#活著是golang都無(wú)所謂。

當(dāng)然市面上基于消息的分布式解決方案各式各樣,但總體來(lái)說(shuō)都屬于最終一致性方案。如果引入消息通道MQ的不穩(wěn)定性,那還需要在各個(gè)業(yè)務(wù)方引入查詢機(jī)制來(lái)確保消息的ack機(jī)制。舉個(gè)栗子:如果商品服務(wù)已經(jīng)正常扣減庫(kù)存,由于mq問(wèn)題,始終不能正常ack。這個(gè)時(shí)候訂單服務(wù)是否會(huì)主動(dòng)查詢商品服務(wù)是否已經(jīng)正??蹘?kù)存?這個(gè)時(shí)候整個(gè)架構(gòu)可能就非現(xiàn)在這個(gè)樣子了,這個(gè)要是扯起來(lái)又是一篇文章了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容