【坑爹呀!】最終一致性分佈式事務如何保障實際生產中99.99%高可用?

歡迎關注微信公衆號:石杉的架構筆記(id:shishan100)redis

個人新課**《C2C 電商系統微服務架構120天實戰訓練營》在公衆號儒猿技術窩**上線了,感興趣的同窗,能夠點擊下方連接瞭解詳情:算法

《C2C 電商系統微服務架構120天實戰訓練營》數據庫

目錄

1、寫在前面
2、可靠消息最終一致性方案的核心流程
2、可靠消息最終一致性方案的高可用保障生產實踐微信

1、寫在前面

上一篇文章我們聊了聊TCC分佈式事務,對於常見的微服務系統,大部分接口調用是同步的,也就是一個服務直接調用另一個服務的接口。markdown

這個時候,用TCC分佈式事務方案來保證各個接口的調用,要麼一塊兒成功,要麼一塊兒回滾,是比較合適的。網絡

可是在實際系統的開發過程當中,可能服務間的調用是異步的。數據結構

也就是說,一個服務發送一個消息給MQ,即消息中間件,好比RocketMQ、RabbitMQ、Kafka、ActiveMQ等等。架構

而後,另一個服務從MQ消費到一條消息後進行處理。這就成了基於MQ的異步調用了。併發

那麼針對這種基於MQ的異步調用,如何保證各個服務間的分佈式事務呢?app

也就是說,我但願的是基於MQ實現異步調用的多個服務的業務邏輯,要麼一塊兒成功,要麼一塊兒失敗。

這個時候,就要用上可靠消息最終一致性方案,來實現分佈式事務。

你們看看上面那個圖,其實若是不考慮各類高併發、高可用等技術挑戰的話,單從「可靠消息」以及「最終一致性」兩個角度來考慮,這種分佈式事務方案仍是比較簡單的。

2、可靠消息最終一致性方案的核心流程

(1)上游服務投遞消息

若是要實現可靠消息最終一致性方案,通常你能夠本身寫一個可靠消息服務,實現一些業務邏輯。

首先,上游服務須要發送一條消息給可靠消息服務。

這條消息說白了,你能夠認爲是對下游服務一個接口的調用,裏面包含了對應的一些請求參數。

而後,可靠消息服務就得把這條消息存儲到本身的數據庫裏去,狀態爲「待確認」。

接着,上游服務就能夠執行本身本地的數據庫操做,根據本身的執行結果,再次調用可靠消息服務的接口。

若是本地數據庫操做執行成功了,那麼就找可靠消息服務確認那條消息。若是本地數據庫操做失敗了,那麼就找可靠消息服務刪除那條消息。

此時若是是確認消息,那麼可靠消息服務就把數據庫裏的消息狀態更新爲「已發送」,同時將消息發送給MQ。

這裏有一個很關鍵的點,就是更新數據庫裏的消息狀態和投遞消息到MQ。這倆操做,你得放在一個方法裏,並且得開啓本地事務。

啥意思呢?

  • 若是數據庫裏更新消息的狀態失敗了,那麼就拋異常退出了,就別投遞到MQ;

  • 若是投遞MQ失敗報錯了,那麼就要拋異常讓本地數據庫事務回滾。

  • 這倆操做必須得一塊兒成功,或者一塊兒失敗。

若是上游服務是通知刪除消息,那麼可靠消息服務就得刪除這條消息。

(2)下游服務接收消息

下游服務就一直等着從MQ消費消息好了,若是消費到了消息,那麼就操做本身本地數據庫。

若是操做成功了,就反過來通知可靠消息服務,說本身處理成功了,而後可靠消息服務就會把消息的狀態設置爲「已完成」。

(3)如何上游服務對消息的100%可靠投遞?

上面的核心流程你們都看完:一個很大的問題就是,若是在上述投遞消息的過程當中各個環節出現了問題該怎麼辦?

咱們如何保證消息100%的可靠投遞,必定會從上游服務投遞到下游服務?彆着急,下面咱們來逐一分析。

若是上游服務給可靠消息服務發送待確認消息的過程出錯了,那不要緊,上游服務能夠感知到調用異常的,就不用執行下面的流程了,這是沒問題的。

若是上游服務操做完本地數據庫以後,通知可靠消息服務確認消息或者刪除消息的時候,出現了問題。

好比:沒通知成功,或者沒執行成功,或者是可靠消息服務沒成功的投遞消息到MQ。這一系列步驟出了問題怎麼辦?

其實也不要緊,由於在這些狀況下,那條消息在可靠消息服務的數據庫裏的狀態會一直是「待確認」。

此時,咱們在可靠消息服務裏開發一個後臺定時運行的線程,不停的檢查各個消息的狀態。

若是一直是「待確認」狀態,就認爲這個消息出了點什麼問題。

此時的話,就能夠回調上游服務提供的一個接口,問問說,兄弟,這個消息對應的數據庫操做,你執行成功了沒啊?

若是上游服務答覆說,我執行成功了,那麼可靠消息服務將消息狀態修改成「已發送」,同時投遞消息到MQ。

若是上游服務答覆說,沒執行成功,那麼可靠消息服務將數據庫中的消息刪除便可。

經過這套機制,就能夠保證,可靠消息服務必定會嘗試完成消息到MQ的投遞。

(4)如何保證下游服務對消息的100%可靠接收?

那若是下游服務消費消息出了問題,沒消費到?或者是下游服務對消息的處理失敗了,怎麼辦?

其實也不要緊,在可靠消息服務裏開發一個後臺線程,不斷的檢查消息狀態。

若是消息狀態一直是「已發送」,始終沒有變成「已完成」,那麼就說明下游服務始終沒有處理成功。

此時可靠消息服務就能夠再次嘗試從新投遞消息到MQ,讓下游服務來再次處理。

只要下游服務的接口邏輯實現冪等性,保證屢次處理一個消息,不會插入重複數據便可。

(5)如何基於RocketMQ來實現可靠消息最終一致性方案?

在上面的通用方案設計裏,徹底依賴可靠消息服務的各類自檢機制來確保:

  • 若是上游服務的數據庫操做沒成功,下游服務是不會收到任何通知

  • 若是上游服務的數據庫操做成功了,可靠消息服務死活都會確保將一個調用消息投遞給下游服務,並且必定會確保下游服務務必成功處理這條消息。

經過這套機制,保證了基於MQ的異步調用/通知的服務間的分佈式事務保障。

其實阿里開源的RocketMQ,就實現了可靠消息服務的全部功能,核心思想跟上面相似。

只不過RocketMQ爲了保證高併發、高可用、高性能,作了較爲複雜的架構實現,很是的優秀。

有興趣的同窗,本身能夠去查閱RocketMQ對分佈式事務的支持。

3、可靠消息最終一致性方案的高可用保障生產實踐

(1)背景引入

其實上面那套方案和思想,不少同窗應該都知道是怎麼回事兒,咱們也主要就是鋪墊一下這套理論思想。

在實際落地生產的時候,若是沒有高併發場景的,徹底能夠參照上面的思路本身基於某個MQ中間件開發一個可靠消息服務。

若是有高併發場景的,能夠用RocketMQ的分佈式事務支持,上面的那套流程均可以實現。

今天給你們分享的一個核心主題,就是這套方案如何保證99.99%的高可用

其實你們應該發現了這套方案裏保障高可用性最大的一個依賴點,就是MQ的高可用性

任何一種MQ中間件都有一整套的高可用保障機制,不管是RabbitMQ、RocketMQ仍是Kafka。

因此在大公司裏使用可靠消息最終一致性方案的時候,咱們一般對可用性的保障都是依賴於公司基礎架構團隊對MQ的高可用保障。

也就是說,你們應該相信兄弟團隊,99.99%能夠保障MQ的高可用,絕對不會由於MQ集羣總體宕機,而致使公司業務系統的分佈式事務所有沒法運行。

可是現實是很殘酷的,不少中小型的公司,甚至是一些中大型公司,或多或少都遇到過MQ集羣總體故障的場景。

MQ一旦徹底不可用,就會致使業務系統的各個服務之間沒法經過MQ來投遞消息,致使業務流程中斷。

好比最近就有一個朋友的公司,也是作電商業務的,就遇到了MQ中間件在本身公司機器上部署的集羣總體故障不可用,致使依賴MQ的分佈式事務所有沒法跑通,業務流程大量中斷的狀況。

這種狀況,就須要針對這套分佈式事務方案實現一套高可用保障機制。

(2)基於KV存儲的隊列支持的高可用降級方案

你們來看看下面這張圖,這是我曾經指導過朋友的一個公司針對可靠消息最終一致性方案設計的一套高可用保障降級機制。

這套機制不算太複雜,能夠很是簡單有效的保證那位朋友公司的高可用保障場景,一旦MQ中間件出現故障,立馬自動降級爲備用方案。

(1)自行封裝MQ客戶端組件與故障感知

首先第一點,你要作到自動感知MQ的故障接着自動完成降級,那麼必須動手對MQ客戶端進行封裝,發佈到公司Nexus私服上去。

而後公司須要支持MQ降級的業務服務都使用這個本身封裝的組件來發送消息到MQ,以及從MQ消費消息。

在你本身封裝的MQ客戶端組件裏,你能夠根據寫入MQ的狀況來判斷MQ是否故障。

好比說,若是連續10次重試嘗試投遞消息到MQ都發現異常報錯,網絡沒法聯通等問題,說明MQ故障,此時就能夠自動感知以及自動觸發降級開關。

(2)基於kv存儲中隊列的降級方案

若是MQ掛掉以後,要是但願繼續投遞消息,那麼就必須得找一個MQ的替代品。

舉個例子,好比我那位朋友的公司是沒有高併發場景的,消息的量不多,只不過可用性要求高。此時就能夠相似redis的kv存儲中的隊列來進行替代。

因爲redis自己就支持隊列的功能,還有相似隊列的各類數據結構,因此你能夠將消息寫入kv存儲格式的隊列數據結構中去。

ps:關於redis的數據存儲格式、支持的數據結構等基礎知識,請你們自行查閱了,網上一大堆

可是,這裏有幾個大坑,必定要注意一下。

第一個,任何kv存儲的集合類數據結構,建議不要往裏面寫入數據量過大,不然會致使大value的狀況發生,引起嚴重的後果。

所以毫不能在redis裏搞一個key,就拼命往這個數據結構中一直寫入消息,這是確定不行的。

第二個,絕對不能往少數key對應的數據結構中持續寫入數據,那樣會致使熱key的產生,也就是某幾個key特別熱。

你們要知道,通常kv集羣,都是根據key來hash分配到各個機器上的,你要是老寫少數幾個key,會致使kv集羣中的某臺機器訪問太高,負載過大。

基於以上考慮,下面是筆者當時設計的方案:

  • 根據他們天天的消息量,在kv存儲中固定劃分上百個隊列,有上百個key對應。

  • 這樣保證每一個key對應的數據結構中不會寫入過多的消息,並且不會頻繁的寫少數幾個key。

  • 一旦發生了MQ故障,可靠消息服務能夠對每一個消息經過hash算法,均勻的寫入固定好的上百個key對應的kv存儲的隊列中。

同時此時須要經過zk觸發一個降級開關,整個系統在MQ這塊的讀和寫所有立馬降級。

(3)下游服務消費MQ的降級感知

下游服務消費MQ也是經過自行封裝的組件來作的,此時那個組件若是從zk感知到降級開關打開了,首先會判斷本身是否還能繼續從MQ消費到數據?

若是不能了,就開啓多個線程,併發的從kv存儲的各個預設好的上百個隊列中不斷的獲取數據。

每次獲取到一條數據,就交給下游服務的業務邏輯來執行。

經過這套機制,就實現了MQ故障時候的自動故障感知,以及自動降級。若是系統的負載和併發不是很高的話,用這套方案大體是沒沒問題的。

由於在生產落地的過程當中,包括大量的容災演練以及生產實際故障發生時的表現來看,都是能夠有效的保證MQ故障時,業務流程繼續自動運行的。

(4)故障的自動恢復

若是降級開關打開以後,自行封裝的組件須要開啓一個線程,每隔一段時間嘗試給MQ投遞一個消息看看是否恢復了。

若是MQ已經恢復能夠正常投遞消息了,此時就能夠經過zk關閉降級開關,而後可靠消息服務繼續投遞消息到MQ,下游服務在確認kv存儲的各個隊列中已經沒有數據以後,就能夠從新切換爲從MQ消費消息。

(5)更多的業務細節

其實上面說的那套方案主要是一套通用的降級方案,可是具體的落地是要結合各個公司不一樣的業務細節來決定的,不少細節多無法在文章裏體現。

好比說大家要不要保證消息的順序性?是否是涉及到須要根據業務動態,生成大量的key?等等。

此外,這套方案實現起來仍是有必定的成本的,因此建議你們儘量仍是push公司的基礎架構團隊,保證MQ的99.99%可用性,不要宕機。

其次就是根據你們公司的實際對高可用需求來決定,若是感受MQ偶爾宕機也沒事,能夠容忍的話,那麼也不用實現這種降級方案。

可是若是公司領導認爲MQ中間件宕機後,必定要保證業務系統流程繼續運行,那麼仍是要考慮一些高可用的降級方案,好比本文提到的這種。

最後再說一句,真要是一些公司涉及到每秒幾萬幾十萬的高併發請求,那麼對MQ的降級方案會設計的更加的複雜,那就遠遠不是這麼簡單能夠作到的。

END

若有收穫,請幫忙轉發,您的鼓勵是做者最大的動力,謝謝!

一大波微服務、分佈式、高併發、高可用的****原創系列

文章正在路上,歡迎掃描下方二維碼,持續關注:

石杉的架構筆記(id:shishan100)

十餘年BAT架構經驗傾囊相授

相關文章
相關標籤/搜索