前言
- 對(duì)于常見的微服務(wù)系統(tǒng),大部分接口調(diào)用是同步的,也就是一個(gè)服務(wù)直接調(diào)用另外一個(gè)服務(wù)的接口。這個(gè)時(shí)候,用TCC分布式事務(wù)方案來保證各個(gè)接口的調(diào)用,要么一起成功,要么一起回滾,是比較合適的。
- 如果服務(wù)間的調(diào)用是異步的,也就是說,一個(gè)服務(wù)發(fā)送一個(gè)消息給MQ,即消息中間件,比如RocketMQ、RabbitMQ、Kafka等等。然后,另外一個(gè)服務(wù)從MQ消費(fèi)到一條消息后進(jìn)行處理。這就成了基于MQ的異步調(diào)用了。
- 那么針對(duì)這種基于MQ的異步調(diào)用,如何保證各個(gè)服務(wù)間的分布式事務(wù)呢?
- 也就是基于MQ實(shí)現(xiàn)異步調(diào)用的多個(gè)服務(wù)的業(yè)務(wù)邏輯,要么一起成功,要么一起失敗。
-
這個(gè)時(shí)候,就要用上可靠消息最終一致性方案,來實(shí)現(xiàn)分布式事務(wù)。
可靠消息最終一致性分布式事務(wù).png - 如果不考慮各種高并發(fā)、高可用等技術(shù)挑戰(zhàn)的話,單從“可靠消息”以及“最終一致性”兩個(gè)角度來考慮,這種分布式事務(wù)方案還是比較簡單的。
可靠消息最終一致性方案的核心流程
上游服務(wù)投遞消息
- 如果要實(shí)現(xiàn)可靠消息最終一致性方案,一般自己寫一個(gè)可靠消息服務(wù),實(shí)現(xiàn)一些業(yè)務(wù)邏輯。
- 首先,上游服務(wù)需要發(fā)送一條消息給可靠消息服務(wù)。
- 你可以認(rèn)為是對(duì)下游服務(wù)一個(gè)接口的調(diào)用,里面包含了對(duì)應(yīng)的一些請(qǐng)求參數(shù)。
- 然后,可靠消息服務(wù)就得把這條消息存儲(chǔ)到自己的數(shù)據(jù)庫里去,狀態(tài)為“待確認(rèn)”。
- 接著,上游服務(wù)就可以執(zhí)行自己本地的數(shù)據(jù)庫操作,根據(jù)自己的執(zhí)行結(jié)果,再次調(diào)用可靠消息服務(wù)的接口。
- 如果本地?cái)?shù)據(jù)庫操作執(zhí)行成功了,那么就找可靠消息服務(wù)確認(rèn)那條消息。如果本地?cái)?shù)據(jù)庫操作失敗了,那么就找可靠消息服務(wù)刪除那條消息。
- 此時(shí)如果是確認(rèn)消息,那么可靠消息服務(wù)就把數(shù)據(jù)庫里的消息狀態(tài)更新為“已發(fā)送”,同時(shí)將消息發(fā)送給MQ。
- 這里有個(gè)很關(guān)鍵的點(diǎn),就是更新數(shù)據(jù)庫里的消息狀態(tài)和投遞消息到MQ。這倆操作,得放在一個(gè)方法里,而且得開啟本地事務(wù)。
- 如果數(shù)據(jù)庫里更新消息的狀態(tài)失敗了,那么就拋異常退出了,就別投遞到MQ;
- 如果投遞MQ失敗報(bào)錯(cuò)了,那么就要拋異常讓本地?cái)?shù)據(jù)庫事務(wù)回滾。
- 這倆操作必須得一起成功,或者一起失敗。
- 如果上游服務(wù)是通知?jiǎng)h除消息,那么可靠消息服務(wù)就得刪除這條消息。
下游服務(wù)接收消息
- 下游服務(wù)就一直等著從MQ消費(fèi)消息,如果消費(fèi)到了消息,那么就操作自己本地?cái)?shù)據(jù)庫。
- 如果操作成功了,就反過來通知可靠消息服務(wù),說自己處理成功了,然后可靠消息服務(wù)就會(huì)把消息的狀態(tài)設(shè)置為“已完成”。
如何上游服務(wù)對(duì)消息的100%可靠投遞?
- 如果投遞消息的過程中各個(gè)環(huán)節(jié)出現(xiàn)了問題該怎么辦?
- 如果上游服務(wù)給可靠消息服務(wù)發(fā)送待確認(rèn)消息的過程出錯(cuò)了,那沒關(guān)系,上游服務(wù)可以感知到調(diào)用異常的,就不用執(zhí)行下面的流程了,這是沒問題的。
- 如果上游服務(wù)操作完本地?cái)?shù)據(jù)庫之后,通知可靠消息服務(wù)確認(rèn)消息或者刪除消息的時(shí)候,出現(xiàn)了問題。
- 比如:沒通知成功,或者沒執(zhí)行成功,或者是可靠消息服務(wù)沒成功的投遞消息到MQ。這一系列步驟出了問題怎么辦?
- 那條消息在可靠消息服務(wù)的數(shù)據(jù)庫里的狀態(tài)會(huì)一直是“待確認(rèn)”。
- 我們可以在在可靠消息服務(wù)里開發(fā)一個(gè)后臺(tái)定時(shí)運(yùn)行的線程,不停的檢查各個(gè)消息的狀態(tài)。
- 如果一直是“待確認(rèn)”狀態(tài),就認(rèn)為這個(gè)消息出了點(diǎn)什么問題。
- 此時(shí)的話,就可以回調(diào)上游服務(wù)提供的一個(gè)接口,這個(gè)消息對(duì)應(yīng)的數(shù)據(jù)庫操作,執(zhí)行成功了?
- 如果上游服務(wù)答復(fù)執(zhí)行成功,那么可靠消息服務(wù)將消息狀態(tài)修改為“已發(fā)送”,同時(shí)投遞消息到MQ。
- 如果上游服務(wù)答復(fù)沒執(zhí)行成功,那么可靠消息服務(wù)將數(shù)據(jù)庫中的消息刪除即可。
- 通過這套機(jī)制,就可以保證,可靠消息服務(wù)一定會(huì)嘗試完成消息到MQ的投遞。
如何保證下游服務(wù)對(duì)消息的100%可靠接收?
- 如果下游服務(wù)消費(fèi)消息出了問題,沒消費(fèi)到?或者是下游服務(wù)對(duì)消息的處理失敗了,怎么辦?
- 在可靠消息服務(wù)里開發(fā)一個(gè)后臺(tái)線程,不斷的檢查消息狀態(tài)。
- 如果消息狀態(tài)一直是“已發(fā)送”,始終沒有變成“已完成”,那么就說明下游服務(wù)始終沒有處理成功。
- 此時(shí)可靠消息服務(wù)就可以再次嘗試重新投遞消息到MQ,讓下游服務(wù)來再次處理。
- 只要下游服務(wù)的接口邏輯實(shí)現(xiàn)冪等性,保證多次處理一個(gè)消息,不會(huì)插入重復(fù)數(shù)據(jù)即可。
