一. 生產(chǎn)端的可靠性投遞
1. 保障消息的成功發(fā)出
2. 保障MQ節(jié)點的成功接收
3. 發(fā)送端收到MQ節(jié)點(broker)確認應(yīng)答
4. 完善的消息補償機制
在實際生產(chǎn)中箭窜,很難保障前三點的完全可靠靶壮,比如在極端的環(huán)境中,生產(chǎn)者發(fā)送消息失敗了坪创,發(fā)送端在接受確認應(yīng)答時突然發(fā)生網(wǎng)絡(luò)閃斷等等情況又碌,很難保障可靠性投遞,所以就需要有第四點完善的消息補償機制兜粘。
二袖扛、互聯(lián)網(wǎng)大廠的解決方案
第一種:消息落庫砸泛,對消息狀態(tài)進行達標(biāo)十籍。具體來說就是將消息持久化到數(shù)據(jù)庫并設(shè)置狀態(tài)值蛆封,收到消費端的應(yīng)答就改變當(dāng)前記錄的狀態(tài)。再用輪詢?nèi)ブ匦掳l(fā)送沒接收到應(yīng)答的消息勾栗,注意這里要設(shè)置重試次數(shù)惨篱。
第二種:消息的延遲投遞,做二次確認围俘,回調(diào)檢查砸讳。
三、消息落庫界牡,對消息狀態(tài)進行打標(biāo)
消息落庫的流程圖
流程的示意圖如上所示簿寂,比如我下單成功了,這是進行 step1宿亡,對我的業(yè)務(wù)數(shù)據(jù)進行入庫常遂,業(yè)務(wù)數(shù)據(jù)入庫完畢(這里要特別注意一定要保證業(yè)務(wù)數(shù)據(jù)入庫)再對要發(fā)送的消息進行入庫,圖中采用了兩個數(shù)據(jù)庫挽荠,可以根據(jù)實際業(yè)務(wù)場景來確定是否采用兩個數(shù)據(jù)庫克胳,如果采用了兩個數(shù)據(jù)庫,有人可能就像到了采用分布式事務(wù)來保證數(shù)據(jù)的一致性圈匆,但是在大型互聯(lián)網(wǎng)中漠另,基本很少采用事務(wù),都是采用補償機制跃赚。對業(yè)務(wù)數(shù)據(jù)和消息入庫完畢就進入 setp2笆搓,發(fā)送消息到 MQ 服務(wù)上,按照正常的流程就是消費者監(jiān)聽到該消息,就根據(jù)唯一 id 修改該消息的狀態(tài)為已消費满败,并給一個確認應(yīng)答 ack 到 Listener窘奏。如果出現(xiàn)意外情況,消費者未接收到或者 Listener 接收確認時發(fā)生網(wǎng)絡(luò)閃斷葫录,接收不到着裹,這時候就需要用到我們的分布式定時任務(wù)來從 msg 數(shù)據(jù)庫抓取那些超時了還未被消費的消息,重新發(fā)送一遍米同。重試機制里面要設(shè)置重試次數(shù)限制骇扇,因為一些外部的原因?qū)е乱恢卑l(fā)送失敗的,不能重試太多次面粮,要不然會拖垮整個服務(wù)少孝。例如重試三次還是失敗的,就把消息的 status 設(shè)置成 2熬苍,然后通過補償機制稍走,人工去處理。實際生產(chǎn)中柴底,這種情況還是比較少的婿脸,但是你不能沒有這個補償機制,要不然就做不到可靠性了柄驻。
四狐树、延遲投遞,做二次確認鸿脓,回調(diào)檢查抑钟。
回想第一種方案,生產(chǎn)端既要對業(yè)務(wù)數(shù)據(jù)入庫野哭,又要對消息數(shù)據(jù)入庫在塔,這種設(shè)計在高并發(fā)場景下,真的合適嗎拨黔?在核心鏈路上蛔溃,每一次持久化都是需要很精心考量的,持久化一次就花費 100 - 200 毫秒蓉驹,這在高并發(fā)場景下是忍受不了的城榛。這時候需要我們的第二種方案了,流程圖如下态兴。
upstream Server 就是我們的上游服務(wù)狠持,也就是生產(chǎn)者,生產(chǎn)者將業(yè)務(wù)數(shù)據(jù)入庫成功后瞻润,生成兩條消息喘垂,一條是立即發(fā)送出去給到下游服務(wù) downstream Server的甜刻,一條是延遲消息給到 補償服務(wù) callback Server的。
正常情況下正勒,下游服務(wù)監(jiān)聽到這個即時的消息得院,會發(fā)送一條消息給到 callback Server,注意這里不是采用第一種方案里面的返回 ack 方式章贞,而是發(fā)送了一條消息給回去祥绞。
callback Server 監(jiān)聽到這個消息,知道了剛才有一條消息消費成功了鸭限,然后把這個持久化到數(shù)據(jù)庫中蜕径,當(dāng)上游服務(wù)發(fā)送的延遲消息到達 callback Server 時,callback Server 就會去數(shù)據(jù)庫查詢败京,剛才下游服務(wù)是否有處理過這個對應(yīng)的消息兜喻,如果其 msg DB 里面有這個記錄就說明這條消息是已經(jīng)被消費了,如果不存在這個記錄赡麦,那么 callback Server 就會發(fā)起一個 RPC 請求給到上游服務(wù)朴皆,告訴上游服務(wù),你剛才這個消息沒發(fā)送成功泛粹,需要重新發(fā)送一遍遂铡,上游服務(wù)就重新發(fā)送即時和延遲的兩條消息出去,按照之前的流程繼續(xù)走一遍戚扳。
雖然第二種方案也是無法做到 100% 的可靠傳遞忧便,在特別極端的情況族吻,還是需要定時任務(wù)和補償機制進行輔助的帽借。但是第二種方案的核心是減少數(shù)據(jù)庫操作,這個點很重要超歌!
在高并發(fā)場景下砍艾,我考慮的不是百分百的可靠性了,而是考慮可用性巍举,性能能否扛得住這個流量脆荷,所以我能減少一次數(shù)據(jù)庫操作就減少一次。我上游服務(wù)減少了一次數(shù)據(jù)庫操作懊悯,我的服務(wù)性能相對而言就提高了一些蜓谋,而且又能把異步 callback Server 補償服務(wù)解耦出來。
五炭分、結(jié)論
這兩種方案都是可行的桃焕,需要根據(jù)實際業(yè)務(wù)來進行選擇,大型的超高并發(fā)的場景會選擇第二種方案捧毛,普通的就采用第一種即可观堂。