如何保證消息的可靠性傳輸劲绪?或者說(shuō)男窟,如何處理消息丟失的問(wèn)題?

面試官心理分析

這個(gè)是肯定的贾富,用 MQ 有個(gè)基本原則歉眷,就是數(shù)據(jù)不能多一條,也不能少一條颤枪,不能多汗捡,就是前面說(shuō)的重復(fù)消費(fèi)和冪等性問(wèn)題。不能少畏纲,就是說(shuō)這數(shù)據(jù)別搞丟了扇住。那這個(gè)問(wèn)題你必須得考慮一下。

如果說(shuō)你這個(gè)是用 MQ 來(lái)傳遞非常核心的消息盗胀,比如說(shuō)計(jì)費(fèi)艘蹋、扣費(fèi)的一些消息,那必須確保這個(gè) MQ 傳遞過(guò)程中絕對(duì)不會(huì)把計(jì)費(fèi)消息給弄丟读整。


面試題剖析

數(shù)據(jù)的丟失問(wèn)題簿训,可能出現(xiàn)在生產(chǎn)者、MQ米间、消費(fèi)者中强品,咱們從 RabbitMQ 和 Kafka 分別來(lái)分析一下吧。

生產(chǎn)者弄丟了數(shù)據(jù)

生產(chǎn)者將數(shù)據(jù)發(fā)送到 RabbitMQ 的時(shí)候屈糊,可能數(shù)據(jù)就在半路給搞丟了的榛,因?yàn)榫W(wǎng)絡(luò)問(wèn)題啥的,都有可能逻锐。

此時(shí)可以選擇用 RabbitMQ 提供的事務(wù)功能夫晌,就是生產(chǎn)者發(fā)送數(shù)據(jù)之前開啟 RabbitMQ 事務(wù)

channel.txSelect,然后發(fā)送消息昧诱,如果消息沒(méi)有成功被 RabbitMQ 接收到晓淀,那么生產(chǎn)者會(huì)收到異常報(bào)錯(cuò),此時(shí)就可以回滾事務(wù) channel.txRollback盏档,然后重試發(fā)送消息凶掰;如果收到了消息,那么可以提交事務(wù)channel.txCommit。

// 開啟事務(wù)

channel.txSelecttry {

// 這里發(fā)送消息

} catch (Exception e) {

channel.txRollback

// 這里再次重發(fā)這條消息

}

// 提交事務(wù)

channel.txCommit

但是問(wèn)題是懦窘,RabbitMQ 事務(wù)機(jī)制(同步)一搞前翎,基本上吞吐量會(huì)下來(lái),因?yàn)樘男阅堋?/b>

所以一般來(lái)說(shuō)畅涂,如果你要確保說(shuō)寫 RabbitMQ 的消息別丟港华,可以開啟 confirm 模式,在生產(chǎn)者那里設(shè)置開啟 confirm 模式之后午衰,你每次寫的消息都會(huì)分配一個(gè)唯一的 id立宜,然后如果寫入了 RabbitMQ 中,RabbitMQ 會(huì)給你回傳一個(gè) ack 消息苇经,告訴你說(shuō)這個(gè)消息 ok 了赘理。如果 RabbitMQ 沒(méi)能處理這個(gè)消息,會(huì)回調(diào)你的一個(gè) nack 接口扇单,告訴你這個(gè)消息接收失敗商模,你可以重試。而且你可以結(jié)合這個(gè)機(jī)制自己在內(nèi)存里維護(hù)每個(gè)消息 id 的狀態(tài)蜘澜,如果超過(guò)一定時(shí)間還沒(méi)接收到這個(gè)消息的回調(diào)施流,那么你可以重發(fā)。

事務(wù)機(jī)制和 confirm 機(jī)制最大的不同在于鄙信,事務(wù)機(jī)制是同步的瞪醋,你提交一個(gè)事務(wù)之后會(huì)阻塞在那兒,但是 confirm 機(jī)制是異步的装诡,你發(fā)送個(gè)消息之后就可以發(fā)送下一個(gè)消息银受,然后那個(gè)消息 RabbitMQ 接收了之后會(huì)異步回調(diào)你的一個(gè)接口通知你這個(gè)消息接收到了。

所以一般在生產(chǎn)者這塊避免數(shù)據(jù)丟失鸦采,都是用 confirm 機(jī)制的宾巍。

RabbitMQ 弄丟了數(shù)據(jù)

就是 RabbitMQ 自己弄丟了數(shù)據(jù),這個(gè)你必須開啟 RabbitMQ 的持久化渔伯,就是消息寫入之后會(huì)持久化到磁盤顶霞,哪怕是 RabbitMQ 自己掛了,恢復(fù)之后會(huì)自動(dòng)讀取之前存儲(chǔ)的數(shù)據(jù)锣吼,一般數(shù)據(jù)不會(huì)丟选浑。除非極其罕見的是,RabbitMQ 還沒(méi)持久化玄叠,自己就掛了古徒,可能導(dǎo)致少量數(shù)據(jù)丟失,但是這個(gè)概率較小读恃。

設(shè)置持久化有兩個(gè)步驟

????????????????創(chuàng)建 queue 的時(shí)候?qū)⑵湓O(shè)置為持久化? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 這樣就可以保證 RabbitMQ 持久化 queue 的元數(shù)據(jù)描函,但是它是不會(huì)持久化 queue 里的數(shù)據(jù)的崎苗。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 第二個(gè)是發(fā)送消息的時(shí)候?qū)⑾⒌?deliveryMode 設(shè)置為 2就是將消息設(shè)置為持久化的,此時(shí) RabbitMQ 就會(huì)將消息持久化到磁盤上去舀寓。

必須要同時(shí)設(shè)置這兩個(gè)持久化才行,RabbitMQ 哪怕是掛了肌蜻,再次重啟互墓,也會(huì)從磁盤上重啟恢復(fù) queue,恢復(fù)這個(gè) queue 里的數(shù)據(jù)蒋搜。

注意篡撵,哪怕是你給 RabbitMQ 開啟了持久化機(jī)制,也有一種可能豆挽,就是這個(gè)消息寫到了 RabbitMQ 中育谬,但是還沒(méi)來(lái)得及持久化到磁盤上,結(jié)果不巧帮哈,此時(shí) RabbitMQ 掛了膛檀,就會(huì)導(dǎo)致內(nèi)存里的一點(diǎn)點(diǎn)數(shù)據(jù)丟失。

所以娘侍,持久化可以跟生產(chǎn)者那邊的 confirm 機(jī)制配合起來(lái)咖刃,只有消息被持久化到磁盤之后,才會(huì)通知生產(chǎn)者 ack 了憾筏,所以哪怕是在持久化到磁盤之前嚎杨,RabbitMQ 掛了,數(shù)據(jù)丟了氧腰,生產(chǎn)者收不到 ack枫浙,你也是可以自己重發(fā)的。

消費(fèi)端弄丟了數(shù)據(jù)

RabbitMQ 如果丟失了數(shù)據(jù)古拴,主要是因?yàn)槟阆M(fèi)的時(shí)候箩帚,剛消費(fèi)到,還沒(méi)處理斤富,結(jié)果進(jìn)程掛了膏潮,比如重啟了,那么就尷尬了满力,RabbitMQ 認(rèn)為你都消費(fèi)了焕参,這數(shù)據(jù)就丟了。

這個(gè)時(shí)候得用 RabbitMQ 提供的 ack 機(jī)制油额,簡(jiǎn)單來(lái)說(shuō)叠纷,就是你必須關(guān)閉 RabbitMQ 的自動(dòng) ack,可以通過(guò)一個(gè) api 來(lái)調(diào)用就行潦嘶,然后每次你自己代碼里確保處理完的時(shí)候涩嚣,再在程序里 ack 一把。這樣的話,如果你還沒(méi)處理完航厚,不就沒(méi)有 ack 了顷歌?那 RabbitMQ 就認(rèn)為你還沒(méi)處理完,這個(gè)時(shí)候 RabbitMQ 會(huì)把這個(gè)消費(fèi)分配給別的 consumer 去處理幔睬,消息是不會(huì)丟的眯漩。


Kafka

消費(fèi)端弄丟了數(shù)據(jù)

唯一可能導(dǎo)致消費(fèi)者弄丟數(shù)據(jù)的情況,就是說(shuō)麻顶,你消費(fèi)到了這個(gè)消息赦抖,然后消費(fèi)者那邊自動(dòng)提交了 offset,讓 Kafka 以為你已經(jīng)消費(fèi)好了這個(gè)消息辅肾,但其實(shí)你才剛準(zhǔn)備處理這個(gè)消息队萤,你還沒(méi)處理,你自己就掛了矫钓,此時(shí)這條消息就丟咯要尔。

這不是跟 RabbitMQ 差不多嗎,大家都知道 Kafka 會(huì)自動(dòng)提交 offset份汗,那么只要關(guān)閉自動(dòng)提交 offset盈电,在處理完之后自己手動(dòng)提交 offset,就可以保證數(shù)據(jù)不會(huì)丟杯活。但是此時(shí)確實(shí)還是可能會(huì)有重復(fù)消費(fèi)匆帚,比如你剛處理完,還沒(méi)提交 offset旁钧,結(jié)果自己掛了吸重,此時(shí)肯定會(huì)重復(fù)消費(fèi)一次,自己保證冪等性就好了歪今。

生產(chǎn)環(huán)境碰到的一個(gè)問(wèn)題嚎幸,就是說(shuō)我們的 Kafka 消費(fèi)者消費(fèi)到了數(shù)據(jù)之后是寫到一個(gè)內(nèi)存的 queue 里先緩沖一下,結(jié)果有的時(shí)候寄猩,你剛把消息寫入內(nèi)存 queue嫉晶,然后消費(fèi)者會(huì)自動(dòng)提交 offset。然后此時(shí)我們重啟了系統(tǒng)田篇,就會(huì)導(dǎo)致內(nèi)存 queue 里還沒(méi)來(lái)得及處理的數(shù)據(jù)就丟失了替废。

Kafka 弄丟了數(shù)據(jù)

這塊比較常見的一個(gè)場(chǎng)景,就是 Kafka 某個(gè) broker 宕機(jī)泊柬,然后重新選舉 partition 的 leader椎镣。大家想想,要是此時(shí)其他的 follower 剛好還有些數(shù)據(jù)沒(méi)有同步兽赁,結(jié)果此時(shí) leader 掛了状答,然后選舉某個(gè) follower成 leader 之后冷守,不就少了一些數(shù)據(jù)?這就丟了一些數(shù)據(jù)啊惊科。

生產(chǎn)環(huán)境也遇到過(guò)拍摇,我們也是,之前 Kafka 的 leader 機(jī)器宕機(jī)了馆截,將 follower 切換為 leader 之后授翻,就會(huì)發(fā)現(xiàn)說(shuō)這個(gè)數(shù)據(jù)就丟了。

所以此時(shí)一般是要求起碼設(shè)置如下 4 個(gè)參數(shù):

? 給 topic 設(shè)置 replication.factor 參數(shù):這個(gè)值必須大于 1孙咪,要求每個(gè) partition 必須有至少 2 個(gè)副本。

在 Kafka 服務(wù)端設(shè)置 min.insync.replicas 參數(shù):這個(gè)值必須大于 1巡语,這個(gè)是要求一個(gè) leader至少感知到有至少一個(gè) follower 還跟自己保持聯(lián)系翎蹈,沒(méi)掉隊(duì),這樣才能確保 leader 掛了還有一個(gè) follower 吧男公。

? 在 producer 端設(shè)置 acks=all:這個(gè)是要求每條數(shù)據(jù)荤堪,必須是寫入所有 replica 之后,才能認(rèn)為是寫成功了枢赔。

? 在 producer 端設(shè)置 retries=MAX(很大很大很大的一個(gè)值澄阳,無(wú)限次重試的意思):這個(gè)是要求一旦寫入失敗,就無(wú)限重試踏拜,卡在這里了碎赢。

我們生產(chǎn)環(huán)境就是按照上述要求配置的,這樣配置之后速梗,至少在 Kafka broker 端就可以保證在 leader 所在 broker 發(fā)生故障肮塞,進(jìn)行 leader 切換時(shí),數(shù)據(jù)不會(huì)丟失姻锁。

生產(chǎn)者會(huì)不會(huì)弄丟數(shù)據(jù)枕赵?

如果按照上述的思路設(shè)置了 acks=all,一定不會(huì)丟位隶,要求是拷窜,你的 leader 接收到消息,所有的 follower都同步到了消息之后涧黄,才認(rèn)為本次寫成功了篮昧。如果沒(méi)滿足這個(gè)條件,生產(chǎn)者會(huì)自動(dòng)不斷的重試弓熏,重試無(wú)限次恋谭。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市挽鞠,隨后出現(xiàn)的幾起案子疚颊,更是在濱河造成了極大的恐慌狈孔,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件材义,死亡現(xiàn)場(chǎng)離奇詭異均抽,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)其掂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門油挥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人款熬,你說(shuō)我怎么就攤上這事深寥。” “怎么了贤牛?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵惋鹅,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我殉簸,道長(zhǎng)闰集,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任般卑,我火速辦了婚禮武鲁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蝠检。我一直安慰自己沐鼠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布蝇率。 她就那樣靜靜地躺著迟杂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪本慕。 梳的紋絲不亂的頭發(fā)上排拷,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音锅尘,去河邊找鬼监氢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛藤违,可吹牛的內(nèi)容都是我干的浪腐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼顿乒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼议街!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起璧榄,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤特漩,失蹤者是張志新(化名)和其女友劉穎吧雹,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涂身,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雄卷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蛤售。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丁鹉。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖悴能,靈堂內(nèi)的尸體忽然破棺而出揣钦,到底是詐尸還是另有隱情,我是刑警寧澤漠酿,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布拂盯,位于F島的核電站,受9級(jí)特大地震影響记靡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜团驱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一摸吠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嚎花,春花似錦寸痢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至兵罢,卻和暖如春献烦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卖词。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工巩那, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人此蜈。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓即横,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親裆赵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子东囚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容