溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何保障消息中間件不丟失

發(fā)布時(shí)間:2021-06-23 15:02:35 來源:億速云 閱讀:275 作者:chen 欄目:編程語言

這篇文章主要介紹“如何保障消息中間件不丟失”,在日常操作中,相信很多人在如何保障消息中間件不丟失問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何保障消息中間件不丟失”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

前言

RabbitMQ,RocketMQ,Kafka 等。引入中間件的好處可以起到抗高并發(fā),削峰,業(yè)務(wù)解耦的作用。

如何保障消息中間件不丟失

如上圖

  1. 訂單服務(wù)投遞消息給MQ中間件

  2. 庫存服務(wù)監(jiān)聽MQ中間件消息,從而進(jìn)行消費(fèi)

之前公司在業(yè)務(wù)搭建的時(shí)候用的就是這種MQ解耦機(jī)制,那么如何保障將訂單服務(wù)的消息成功投遞給MQ中間件,保證消息的可靠性?

問題

可能會(huì)有人疑問,訂單服務(wù)發(fā)起消息,返回成功不就OK了嗎?下面有一個(gè)Demo代碼

如何保障消息中間件不丟失

一般的發(fā)送消息都是這么寫的,但是有一個(gè)場景必須在業(yè)務(wù)搭建之初就要考慮。如果MQ服務(wù)器突然宕機(jī)了呢?我們發(fā)送的消息是不是就沒有了呢?

是的! 一般MQ中間件為了保證提供系統(tǒng)的吞吐量會(huì)把消息保存在內(nèi)存中,如果不作其他處理,MQ一旦宕機(jī),消息就會(huì)全部丟失。這個(gè)在業(yè)務(wù)中是絕對不允許的,造成的影響是非常大的!

那么如何解決這個(gè)問題?

消息持久化

MQ中發(fā)消息的時(shí)候會(huì)有一個(gè)durable參數(shù)可以設(shè)置,設(shè)置為true,就會(huì)持久化!

如何保障消息中間件不丟失

這樣的話MQ服務(wù)器及時(shí)宕機(jī),重啟后磁盤文件中有消息的存儲(chǔ),這樣就不會(huì)丟失了,但是這種方式也僅僅只是有概率的消息不丟失。

如果消息剛剛保存到MQ內(nèi)存中,還沒來得及更新到磁盤中,突然宕機(jī)了(一般高并發(fā)情況下發(fā)生幾率會(huì)很高),尤其是大量消息投遞的過程中。

如何才能做到一定持久化到磁盤中呢?

confirm機(jī)制

上面的問題主要在于,沒有人告訴我們持久化是否成功。好在MQ有回調(diào)通知特性,confirm機(jī)制來通知我們是否持久化成功。

如何保障消息中間件不丟失

confirm機(jī)制原理

  • 消息生產(chǎn)者把消息發(fā)送給MQ,如果接收成功,MQ會(huì)返回一個(gè)ack消息- 給生產(chǎn)者。

  • 如果消息不成功,MQ會(huì)返回一個(gè)nack消息給生產(chǎn)者。

如何保障消息中間件不丟失

上面的demo代碼中有兩個(gè)確認(rèn)機(jī)制,一個(gè)ACK回調(diào),一個(gè)NACK回調(diào)。

這樣是不是就可以100%確保消息不丟失了呢?

吞吐量問題嚴(yán)重

試想一下,如果我們生產(chǎn)者每發(fā)一條消息,都要 MQ 持久化到磁盤中,然后再發(fā)起 ack 或 nack 的回調(diào)。這樣的話是不是我們 MQ 的吞吐量很不高,因?yàn)槊看味家严⒊志没酱疟P中。寫入磁盤這個(gè)動(dòng)作是很慢的。這個(gè)在高并發(fā)場景下是不能夠接受的,吞吐量太低了。

所以 MQ 持久化磁盤真實(shí)的實(shí)現(xiàn),是通過異步調(diào)用處理的,他是有一定的機(jī)制,如:等到有幾千條消息的時(shí)候,會(huì)一次性的刷盤到磁盤上面。而不是每來一條消息,就刷盤一次。

所以 comfirm 機(jī)制其實(shí)是一個(gè)異步監(jiān)聽的機(jī)制,是為了保證系統(tǒng)的高吞吐量,這樣就導(dǎo)致了還是不能夠 100%保障消息不丟失,因?yàn)榧词辜由狭?confirm 機(jī)制,消息在 MQ 內(nèi)存中還沒有刷盤到磁盤就宕機(jī)了,還是沒法處理。

消息提前持久化 + 定時(shí)任務(wù)

其實(shí)本質(zhì)的原因是無法確定是否持久化。

ps:圖畫的有點(diǎn)辣雞~~~~

如何保障消息中間件不丟失

流程操作

  1. 訂單服務(wù)生產(chǎn)者再投遞消息之前,先把消息持久化到 Redis 或 DB 中,建議 redis,高性能。消息的狀態(tài)為發(fā)送中

  2. confirm 機(jī)制監(jiān)聽消息是否發(fā)送成功?如 ack 成功消息,刪除 redis 中此消息。

  3. 如果 nack 不成功的消息,這個(gè)可以根據(jù)自身的業(yè)務(wù)選擇是否重發(fā)此消息。也可以刪除此消息,由自己的業(yè)務(wù)決定。

  4. 這邊加了個(gè)定時(shí)任務(wù),來拉取隔一定時(shí)間了,消息狀態(tài)還是為發(fā)送中的,這個(gè)狀態(tài)就表明,訂單服務(wù)是沒有收到 ack 成功消息。

  5. 定時(shí)任務(wù)會(huì)作補(bǔ)償性的投遞消息。這個(gè)時(shí)候如果 MQ 回調(diào) ack 成功接收了,再把 redis 中此消息刪除。

補(bǔ)償機(jī)制方案

這樣的機(jī)制其實(shí)就是一個(gè)補(bǔ)償機(jī)制,我不管 MQ 有沒有真正的接收到,只要我的 redis 中的消息狀態(tài)也是為==發(fā)送中==,就表示此消息沒有正確成功投遞。再啟動(dòng)定時(shí)任務(wù)去監(jiān)控,發(fā)起補(bǔ)償投遞。

機(jī)制的優(yōu)化

當(dāng)然定時(shí)任務(wù)那邊我們還可以加上一個(gè)補(bǔ)償?shù)拇螖?shù),如果大于 3 次,還是沒有收到 ack 消息,那就直接把消息的狀態(tài)設(shè)置為【失敗】,由人工去排查到底是為什么?

這樣的話方案就比較完美了,保障了 100%的消息不丟失、磁盤要是壞了,那就沒法保障了,就要考慮集群方案。

方案問題

不過這樣的方案,就會(huì)有可能發(fā)送多次相同的消息,很有可能 MQ 已經(jīng)收到了消息,就是 ack 消息回調(diào)時(shí)出現(xiàn)網(wǎng)絡(luò)故障,沒有讓生產(chǎn)者收到。那就要要求消費(fèi)者一定在消費(fèi)的時(shí)候保障冪等性。

冪等含義

我們先了解一下什么叫冪等?在分布式應(yīng)用中,冪等是非常重要的,也就是相同條件下對一個(gè)業(yè)務(wù)的操作,不管操作多少次,結(jié)果都是一樣。

分布式冪等

為什么要有冪等這種場景?因?yàn)樵诖蟮南到y(tǒng)中,都是分布式部署,如:訂單業(yè)務(wù)庫存業(yè)務(wù) 有可能都是獨(dú)立部署的,都是單獨(dú)的服務(wù)。用戶下訂單,會(huì)調(diào)用到訂單服務(wù)和庫存服務(wù)。

分布式異常問題

因?yàn)榉植际讲渴?,很有可能在調(diào)用庫存服務(wù)時(shí),因?yàn)榫W(wǎng)絡(luò)等原因,訂單服務(wù)調(diào)用失敗,但其實(shí)庫存服務(wù)已經(jīng)處理完成。只是返回給訂單服務(wù)處理結(jié)果時(shí)出現(xiàn)了異常。這個(gè)時(shí)候一般系統(tǒng)會(huì)作補(bǔ)償方案,也就是訂單服務(wù)再此放起庫存服務(wù)的調(diào)用,庫存減 1。

update m_goods set count = count - 1 where g_id=10

這樣就出現(xiàn)了問題,其實(shí)上一次調(diào)用已經(jīng)減了 1,只是訂單服務(wù)沒有收到處理結(jié)果。現(xiàn)在又調(diào)用一次,又要減 1,這樣就不符合業(yè)務(wù)了,多扣了。

冪等這個(gè)概念就是,不管庫存服務(wù)在相同條件下調(diào)用幾次,處理結(jié)果都一樣。這樣才能保證補(bǔ)償方案的可行性。

樂觀鎖方案

借鑒網(wǎng)上的樂觀鎖方案,例如:

update m_goods set count = count -1 , version = version + 1 where g_id=2 and version = 1

根據(jù) version 版本,也就是在操作庫存前先獲取當(dāng)前商品的 version 版本號(hào),然后操作的時(shí)候帶上此 version 號(hào)。我們梳理下,我們第一次操作庫存時(shí),得到 version 結(jié)果如下:

  1. 調(diào)用庫存服務(wù) version 變成了 2;但返回給訂單服務(wù)出現(xiàn)了問題,訂單服務(wù)又一次發(fā)起調(diào)用庫存服務(wù),當(dāng)訂單服務(wù)傳如的 version 還是 1,再執(zhí)行上面的 sql 語句時(shí),就不會(huì)執(zhí)行;因?yàn)?version 已經(jīng)變?yōu)?2 了,where 條件就不成立。這樣就保證了不管調(diào)用幾次,只會(huì)真正的處理一次。

唯一 ID + 指紋碼

此方案是網(wǎng)上找到的一篇博客所寫的,感覺不錯(cuò)。

原理就是利用數(shù)據(jù)庫主鍵去重,業(yè)務(wù)完成后插入主鍵標(biāo)識(shí)~

select count(1) from t_check where ID = 唯一ID + 指紋碼
  • 唯一 ID 就是業(yè)務(wù)表的唯一的主鍵,如商品 ID

  • 指紋碼就是為了區(qū)別每次正常操作的碼,每次操作時(shí)生成指紋碼;可以用時(shí)間戳+業(yè)務(wù)編號(hào)的方式。

上面的 sql 語句:

  • 返回如果為 0 表示沒有操作過,那業(yè)務(wù)操作后就可以 insert into t_check(唯一 ID+指紋碼)

  • 返回如果大于 0 表示操作過,就直接返回

好處:實(shí)現(xiàn)簡單

壞處:高并發(fā)下數(shù)據(jù)庫瓶頸

解決方案:根據(jù) ID 進(jìn)行分庫分表進(jìn)行算法路由

redis 原子操作(推薦使用)

利用 redis 的原子操作,做個(gè)操作完成的標(biāo)記。這個(gè)性能就比較好。但會(huì)遇到一些問題。

問題:我們是否需要把業(yè)務(wù)結(jié)果進(jìn)行數(shù)據(jù)落庫,如果落庫,關(guān)鍵解決的問題時(shí)數(shù)據(jù)庫和 redis 操作如何做到原子性?

這個(gè)意思就是庫存減 1 了,但 redis 進(jìn)行操作完成標(biāo)記時(shí),失敗了怎么辦?也就是一定要保證落庫和 redis 要么一起成功,要么一起失敗

第二:如果不進(jìn)行落庫,那么都存儲(chǔ)到緩存中,如何設(shè)置定時(shí)同步策略?

這個(gè)意思就是庫存減 1,不落庫,直接先操作 redis 操作完成標(biāo)記,然后由另外的同步服務(wù)進(jìn)行庫存落庫,這個(gè)就是增加了系統(tǒng)復(fù)雜性,而且同步策略的設(shè)置。

到此,關(guān)于“如何保障消息中間件不丟失”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI