前面我們講了分布式事務(wù)的2PC、3PC , TCC 的原理。這些事務(wù)其實都在盡力的模擬數(shù)據(jù)庫的事務(wù),我們可以簡單的認為他們是一個同步行的事務(wù)。特別是 2PC,3PC 他們完全利用數(shù)據(jù)庫的事務(wù)能力,在一階段開始事務(wù)后不進提交會嚴重影響應(yīng)用程序的并發(fā)性能。TCC 一階段雖然不會阻塞數(shù)據(jù)庫,但是它同樣是在盡力追求同時成功同時失敗的一致性要求。但是在很多時候,我們的應(yīng)用程序的核心業(yè)務(wù)為了追求更高的性能、更高的可用性,可以允許在一段時間內(nèi)的數(shù)據(jù)不一致性,只需要在最終時刻數(shù)據(jù)是一致就可以了?;谝陨蠄鼍拔覀兛梢圆捎没诳煽肯⒎?wù)的最終一致性分布式事務(wù)處理方案。
總體架構(gòu)
可靠消息最終一致性的架構(gòu)上分3個部分:
- 主動方:主動發(fā)起事務(wù)的一方,一般是指分布式事務(wù)中最先開始執(zhí)行的那個服務(wù),也是核心業(yè)務(wù)服務(wù)
- 可靠消息服務(wù):可靠消息服務(wù)由關(guān)系型數(shù)據(jù)庫、消息隊列MQ等組件組成,利用關(guān)系型數(shù)據(jù)庫的強一致性特性來持久化消息的狀態(tài),利用MQ來保證消息的可靠投遞及消費
- 被動方:被動方訂閱MQ的消息,當收到MQ的消息后執(zhí)行對應(yīng)的業(yè)務(wù)
以上是比較粗狂的結(jié)構(gòu)圖,下面我們來詳細分析一下這個事務(wù)的執(zhí)行過程。
流程
該方案總體流程上可分為以下步驟:
- 主動方在真正的業(yè)務(wù)開始前先向可靠消息服務(wù)發(fā)送一個“待確認”的消息
- 可靠消息服務(wù)收到待確認消息后持久化消息到數(shù)據(jù)庫
- 如果以上操作成功則主動方開始真正的業(yè)務(wù),如果失敗則直接放棄執(zhí)行業(yè)務(wù)
- 如果業(yè)務(wù)執(zhí)行成功則發(fā)送“確認”消息給可靠消息服務(wù),如果執(zhí)行失敗則發(fā)送“取消”給可靠消息服務(wù)。
- 如果可靠消息服務(wù)收到“確認”消息則更新數(shù)據(jù)庫里的消息記錄的狀態(tài)為“待發(fā)送”,如果收到的消息為“取消”則更新消息狀態(tài)為“已取消”
- 如果上一步更新的數(shù)據(jù)庫為“待發(fā)送”,那么會開始往MQ投遞消息,并且更改數(shù)據(jù)庫里的消息記錄的狀態(tài)為“已發(fā)送”
- 上一步往MQ投遞消息成功后,MQ會給被動方推送消息。
- 被動方收到消息后開始處理業(yè)務(wù)
- 如果業(yè)務(wù)處理成功,則被動方對MQ進行ACK回復,則這條消息會從MQ內(nèi)移除掉
- 如果業(yè)務(wù)處理成功,則發(fā)送“已完成”消息給可靠消息服務(wù)
- 可靠消息服務(wù)收到“已完成”消息后更新數(shù)據(jù)庫消息記錄未“已完成”
異常處理
以上我們描述的是一套在理想情況下執(zhí)行的邏輯。但是分布式系統(tǒng)由于網(wǎng)絡(luò)的存在,網(wǎng)絡(luò)的不可靠性會導致我們消息的傳遞沒辦法100%成功。我們的可靠消息服務(wù)跟主動方、被動方之間的交互也是分布式的,這就需要我們在流程上有很多補償?shù)臋C制。以下我們來討論一些異常情況:
- 如果步驟1發(fā)送“待確認”消息失敗,主動方業(yè)務(wù)不會執(zhí)行,直接放棄事務(wù),不會有影響
- 如果步驟1發(fā)送“待確認”消息成功,并且可靠消息已經(jīng)更新“待確認”成功,但是由于網(wǎng)絡(luò)問題,比如超時,主動方得到的結(jié)果是失敗,主動方會放棄執(zhí)行事務(wù),標記為“已取消”。這個時候就會出現(xiàn)可靠消息服務(wù)跟主動方的狀態(tài)出現(xiàn)不一致的情況。
為解決這個問題,我需要主動方提供一個事務(wù)狀態(tài)查詢接口,可靠消息服務(wù)這邊則啟動一個定時任務(wù),定時去查這些長時間處于待確認的事務(wù),然后通過主動方的接口確認這些事務(wù)是已執(zhí)行,還是已取消。 - 如果步驟4,主動方發(fā)送“確認”消息失敗,可靠消息服務(wù)會通過定時任務(wù)通過主動方的查詢接口去確認狀態(tài)。
- 步驟6,投遞消息給MQ跟更新狀態(tài)為“已發(fā)送”,是這個流程中至關(guān)重要的一步。理想的流程是整個2個操作同時成功同時失敗,保持強一致性。網(wǎng)上很多文章都會說“為了保證發(fā)送MQ消息跟更新消息狀態(tài)同時成功同時失敗,需要把這個2個步驟寫同一個本地事務(wù)中”。這是完全不靠譜的,數(shù)據(jù)庫跟MQ本就是2個獨立的服務(wù),如果通過一個本地的事務(wù)就能保證一致性,那么我們現(xiàn)在討論的分布式事務(wù)毫無意義,直接寫在一個本地事務(wù)里不就完了么。
寫在本地事務(wù)內(nèi)只能盡可能的保證數(shù)據(jù)庫更新跟MQ投遞消息同時成功,但是并不能保證100%一致。
以下我們來分2種情況分析失敗的情況:
事務(wù)開始
1. send to mq
2. database update
事務(wù)結(jié)束
先發(fā)送 MQ 消息,可能 MQ 消息發(fā)送成功,但是database由于某些原因更新失敗了。數(shù)據(jù)庫可以回滾,但是通常的MQ沒有回滾能力。
事務(wù)開始
1. database update
2. send to mq
事務(wù)結(jié)束
先更新數(shù)據(jù)庫,再發(fā)送 MQ 消息,同樣會有問題。就算1,2都執(zhí)行成功了,但是事務(wù)是需要提交的,數(shù)據(jù)庫有可能在提交階段失敗,數(shù)據(jù)庫是可以回滾,但是 MQ 的消息已經(jīng)發(fā)出去了,它并沒有回滾的能力。
為了解決這個問題,我們同樣需要補償機制。在可靠消息服務(wù)一側(cè)開啟定時任務(wù),定時去查詢那些長期處于“待發(fā)送”的事務(wù),再次對 MQ 進行投遞消息。這個機制有可能造成被動方重復收到 MQ 的消息,這就需要被動方處理業(yè)務(wù)的時候要進行冪等處理。
總結(jié)
通過以上我們詳細介紹了可靠消息最終一致性事務(wù)解決方案的總體結(jié)構(gòu)跟執(zhí)行的流程,以及對異常情況的一些補償方法,總體流程上還是比較清晰簡單的。但是可靠消息最終一致性方案在使用上也是具有比較強的局限性,因為它的異步特性跟有可能出現(xiàn)的高延時性不適合處理一些敏感業(yè)務(wù)。比如它適合處理消費新增積分場景,但是不合適處理積分兌換禮品的場景。因為如果積分扣減延遲了,那么用戶就可能兌換超出本身積分多的多的禮品。所以我們選擇分布式事務(wù)的時候還需根據(jù)場景來進行選擇。
好了講了這么多分布式事務(wù)的原理,下一期我們使用 .NET 真正的實現(xiàn)一個分布式事務(wù),敬請期待。
.Net Core with 微服務(wù) - 什么是微服務(wù)
.Net Core with 微服務(wù) - 架構(gòu)圖
.Net Core with 微服務(wù) - Ocelot 網(wǎng)關(guān)
.Net Core with 微服務(wù) - Consul 注冊中心
.Net Core with 微服務(wù) - Seq 日志聚合
.Net Core with 微服務(wù) - Elastic APM
.Net Core with 微服務(wù) - Consul 配置中心
.Net Core with 微服務(wù) - Polly 熔斷降級
.Net Core with 微服務(wù) - 分布式事務(wù) - 2PC、3PC
.Net Core with 微服務(wù) - 分布式事務(wù) - TCC
關(guān)注我的公眾號一起玩轉(zhuǎn)技術(shù)
本文摘自 :https://www.cnblogs.com/