RabbitMQ 100% 投遞成功方案詳解

HmilyMing發表於2019-01-24

一. 生產端的可靠性投遞

1. 保障訊息的成功發出

2. 保障MQ節點的成功接收

3. 傳送端收到MQ節點(broker)確認應答

4. 完善的訊息補償機制
複製程式碼

在實際生產中,很難保障前三點的完全可靠,比如在極端的環境中,生產者傳送訊息失敗了,傳送端在接受確認應答時突然發生網路閃斷等等情況,很難保障可靠性投遞,所以就需要有第四點完善的訊息補償機制。

二、網際網路大廠的解決方案

第一種:訊息落庫,對訊息狀態進行達標。具體來說就是將訊息持久化到資料庫並設定狀態值,收到消費端的應答就改變當前記錄的狀態。再用輪詢去重新傳送沒接收到應答的訊息,注意這裡要設定重試次數。

第二種:訊息的延遲投遞,做二次確認,回撥檢查。
複製程式碼

三、訊息落庫,對訊息狀態進行打標

訊息落庫的流程圖

RabbitMQ 100% 投遞成功方案詳解
流程的示意圖如上所示,比如我下單成功了,這是進行 step1,對我的業務資料進行入庫,業務資料入庫完畢(這裡要特別注意一定要保證業務資料入庫)再對要傳送的訊息進行入庫,圖中採用了兩個資料庫,可以根據實際業務場景來確定是否採用兩個資料庫,如果採用了兩個資料庫,有人可能就像到了採用分散式事務來保證資料的一致性,但是在大型網際網路中,基本很少採用事務,都是採用補償機制。

對業務資料和訊息入庫完畢就進入 setp2,傳送訊息到 MQ 服務上,按照正常的流程就是消費者監聽到該訊息,就根據唯一 id 修改該訊息的狀態為已消費,並給一個確認應答 ack 到 Listener。如果出現意外情況,消費者未接收到或者 Listener 接收確認時發生網路閃斷,接收不到,這時候就需要用到我們的分散式定時任務來從 msg 資料庫抓取那些超時了還未被消費的訊息,重新傳送一遍。重試機制裡面要設定重試次數限制,因為一些外部的原因導致一直髮送失敗的,不能重試太多次,要不然會拖垮整個服務。例如重試三次還是失敗的,就把訊息的 status 設定成 2,然後通過補償機制,人工去處理。實際生產中,這種情況還是比較少的,但是你不能沒有這個補償機制,要不然就做不到可靠性了。

要看程式碼實現的可以去看看我的這個系列:www.jianshu.com/c/c1785aa6c…

四、延遲投遞,做二次確認,回撥檢查。

回想第一種方案,生產端既要對業務資料入庫,又要對訊息資料入庫,這種設計在高併發場景下,真的合適嗎?在核心鏈路上,每一次持久化都是需要很精心考量的,持久化一次就花費 100 - 200 毫秒,這在高併發場景下是忍受不了的。這時候需要我們的第二種方案了,流程圖如下。

RabbitMQ 100% 投遞成功方案詳解

upstream Server 就是我們的上游服務,也就是生產者,生產者將業務資料入庫成功後,生成兩條訊息,一條是立即傳送出去給到下游服務 downstream Server的,一條是延遲訊息給到 補償服務 callback Server的。

正常情況下,下游服務監聽到這個即時的訊息,會傳送一條訊息給到 callback Server,注意這裡不是採用第一種方案裡面的返回 ack 方式,而是傳送了一條訊息給回去。

callback Server 監聽到這個訊息,知道了剛才有一條訊息消費成功了,然後把這個持久化到資料庫中,當上遊服務傳送的延遲訊息到達 callback Server 時,callback Server 就會去資料庫查詢,剛才下游服務是否有處理過這個對應的訊息,如果其 msg DB 裡面有這個記錄就說明這條訊息是已經被消費了,如果不存在這個記錄,那麼 callback Server 就會發起一個 RPC 請求給到上游服務,告訴上游服務,你剛才這個訊息沒傳送成功,需要重新傳送一遍,上游服務就重新傳送即時和延遲的兩條訊息出去,按照之前的流程繼續走一遍。

雖然第二種方案也是無法做到 100% 的可靠傳遞,在特別極端的情況,還是需要定時任務和補償機制進行輔助的。但是第二種方案的核心是減少資料庫操作,這個點很重要!

在高併發場景下,我考慮的不是百分百的可靠性了,而是考慮可用性,效能能否扛得住這個流量,所以我能減少一次資料庫操作就減少一次。我上游服務減少了一次資料庫操作,我的服務效能相對而言就提高了一些,而且又能把非同步 callback Server 補償服務解耦出來。

五、結論

這兩種方案都是可行的,需要根據實際業務來進行選擇,大型的超高併發的場景會選擇第二種方案,普通的就採用第一種即可。

相關文章