互聯網面試必殺:如何保證消息中間件全鏈路數據100%不丟失:第四篇

前情提示

上篇文章:《互聯網面試必殺:如何保證消息中間件全鏈路數據100%不丟失:第三篇》,咱們分析了 RabbitMQ 開啓手動ack機制保證消費端數據不丟失的時候,prefetch 機制對消費者的吞吐量以及內存消耗的影響。html

經過分析,咱們知道了 prefetch 過大容易致使內存溢出,prefetch 太小又會致使消費吞吐量太低,因此在實際項目中須要慎重測試和設置。面試

這篇文章,咱們轉移到消息中間件的生產端,一塊兒來看看如何保證投遞到 MQ 的數據不丟失。數據庫

若是投遞出去的消息在網絡傳輸過程當中丟失,或者在 RabbitMQ 的內存中還沒寫入磁盤的時候宕機,都會致使生產端投遞到MQ的數據丟失。微信

並且丟失以後,生產端本身還感知不到,同時還沒辦法來補救。網絡

下面的圖就展現了這個問題。架構

因此本文呢,咱們就來逐步分析一下。併發

保證投遞消息不丟失的 confirm 機制

其實要解決這個問題,相信你們看過以前的消費端 ack 機制以後,也都猜到了。異步

很簡單,就是生產端(好比上圖的訂單服務)首先須要開啓一個 confirm 模式,接着投遞到 MQ 的消息,若是 MQ 一旦將消息持久化到磁盤以後,必須也要回傳一個 confirm 消息給生產端。高併發

這樣的話,若是生產端的服務接收到了這個 confirm 消息,就知道是已經持久化到磁盤了。性能

不然若是沒有接收到confirm消息,那麼就說明這條消息半路可能丟失了,此時你就能夠從新投遞消息到 MQ 去,確保消息不要丟失。

並且一旦你開啓了confirm模式以後,每次消息投遞也一樣是有一個 delivery tag的,也是起到惟一標識一次消息投遞的做用。

這樣,MQ回傳ack給生產端的時候,會帶上這個 delivery tag。你就知道具體對應着哪一次消息投遞了,能夠刪除這條消息。

此外,若是 RabbitMQ 接收到一條消息以後,結果內部出錯發現沒法處理這條消息,那麼他會回傳一個 nack 消息給生產端。此時你就會感知到這條消息可能處理有問題,你能夠選擇從新再次投遞這條消息到MQ去。

或者另外一種狀況,若是某條消息很長時間都沒給你回傳 ack/nack,那多是極端意外狀況發生了,數據也丟了,你也能夠本身從新投遞消息到 MQ 去。

經過這套 confirm 機制,就能夠實現生產端投遞消息不會丟失的效果。你們來看看下面的圖,一塊兒來感覺一下。

confirm機制的代碼實現

下面,咱們再來看看confirm機制的代碼實現:

confirm機制投遞消息的高延遲性

這裏有一個很關鍵的點,就是一旦啓用了 confirm 機制投遞消息到 MQ 以後,MQ 是不保證何時會給你一個ack或者nack的。

由於 RabbitMQ 本身內部將消息持久化到磁盤,自己就是經過異步批量的方式來進行的。

正常狀況下,你投遞到 RabbitMQ 的消息都會先駐留在內存裏,而後過了幾百毫秒的延遲時間以後,再一次性批量把多條消息持久化到磁盤裏去。

這樣作,是爲了兼顧高併發寫入的吞吐量和性能的,由於要是你來一條消息就寫一次磁盤,那麼性能會不好,每次寫磁盤都是一次 fsync 強制刷入磁盤的操做,是很耗時的。

因此正是由於這個緣由,你打開了 confirm 模式以後,極可能你投遞出去一條消息,要間隔幾百毫秒以後,MQ 纔會把消息寫入磁盤,接着你纔會收到 MQ 回傳過來的 ack 消息,這個就是所謂confirm機制投遞消息的高延遲性。

你們看看下面的圖,一塊兒來感覺一下。

高併發下如何投遞消息才能不丟失

你們能夠考慮一下,在生產端高併發寫入 MQ 的場景下,你會面臨兩個問題:

  • 一、你每次寫一條消息到 MQ,爲了等待這條消息的ack,必須把消息保存到一個存儲裏。

而且這個存儲不建議是內存,由於高併發下消息是不少的,每秒可能都幾千甚至上萬的消息投遞出去,消息的 ack 要等幾百毫秒的話,放內存可能有內存溢出的風險。

  • 二、絕對不能以同步寫消息 + 等待 ack 的方式來投遞,那樣會致使每次投遞一個消息都同步阻塞等待幾百毫秒,會致使投遞性能和吞吐量大幅度降低。

針對這兩個問題,相對應的方案其實也呼之欲出了。

首先,用來臨時存放未 ack 消息的存儲須要承載高併發寫入,並且咱們不須要什麼複雜的運算操做,這種存儲首選絕對不是 MySQL 之類的數據庫,而建議採用 kv 存儲。kv 存儲承載高併發能力極強,並且 kv 操做性能很高。

其次,投遞消息以後等待 ack 的過程必須是異步的,也就是相似上面那樣的代碼,已經給出了一個初步的異步回調的方式。

消息投遞出去以後,這個投遞的線程其實就能夠返回了,至於每一個消息的異步回調,是經過在channel註冊一個confirm監聽器實現的。

收到一個消息 ack 以後,就從kv存儲中刪除這條臨時消息;收到一個消息 nack 以後,就從 kv 存儲提取這條消息而後從新投遞一次便可;也能夠本身對 kv 存儲裏的消息作監控,若是超過必定時長沒收到 ack,就主動重發消息。

你們看看下面的圖,一塊兒來體會一下:

消息中間件全鏈路100%數據不丟失能作到嗎?

到此爲止,咱們已經把生產端和消費端如何保證消息不丟失的相關技術方案結合RabbitMQ這種中間件都給你們分析過了。

其實,架構思想是通用的, 不管你用的是哪種MQ中間件,他們提供的功能是不太同樣的,可是你都須要考慮以下幾點:

  1. 生產端如何保證投遞出去的消息不丟失:消息在半路丟失,或者在 MQ 內存中宕機致使丟失,此時你如何基於 MQ 的功能保證消息不要丟失?

  2. MQ 自身如何保證消息不丟失:起碼須要讓 MQ 對消息是有持久化到磁盤這個機制。

  3. 消費端如何保證消費到的消息不丟失:若是你處理到一半消費端宕機,致使消息丟失,此時怎麼辦?

目前來講,咱們初步的藉着 RabbitMQ 舉例,已經把從前到後一整套技術方案的原理、設計和實現都給你們分析了一遍了。

可是此時真的能作到100%數據不丟失嗎?恐怕未必,你們再考慮一下個特殊的場景。

生產端投遞了消息到 MQ,並且持久化到磁盤而且回傳ack給生產端了。

可是此時 MQ 還沒投遞消息給消費端,結果 MQ 部署的機器忽然宕機,並且由於未知的緣由磁盤損壞了,直接在物理層面致使 MQ 持久化到磁盤的數據找不回來了。

這個你們千萬別覺得是開玩笑的,你們若是留意留意行業新聞,這種磁盤損壞致使數據丟失的是真的有的。

那麼此時即便你把 MQ 重啓了,磁盤上的數據也丟失了,數據是否是仍是丟失了?

你說,我能夠用 MQ 的集羣機制啊,給一個數據作多個副本,好比後面咱們就會給你們分析 RabbitMQ 的鏡像集羣機制,確實能夠作到數據多副本。

可是即便數據多副本,必定能夠作到100%數據不丟失?

好比說你的機房忽然遇到地震,結果機房裏的機器所有沒了,數據是否是仍是全丟了?

說這個,並非說要擡槓。而是告訴你們,技術這個東西,100%都是理論上的指望。

應該說,咱們凡事都朝着100%去作,可是理論上是不可能徹底作到100%保證的,可能就是作到99.9999%的可能性數據不丟失,可是仍是有千萬分之一的機率會丟失。

固然,從實際的狀況來講,能作到這種地步,其實基本上已經基本數據不會丟失了。

互聯網面試必殺:如何保證消息中間件全鏈路數據100%不丟失:第一篇
互聯網面試必殺:如何保證消息中間件全鏈路數據100%不丟失:第二篇
互聯網面試必殺:如何保證消息中間件全鏈路數據100%不丟失:第三篇

來源:【微信公衆號 - 石杉的架構筆記】

相關文章
相關標籤/搜索