高併發場景下,如何保證生產者投遞到訊息中介軟體的訊息不丟失?

石杉的架構筆記發表於2019-01-25

目錄

(1)前情提示

(2)保證投遞訊息不丟失的confirm機制

(3)confirm機制的程式碼實現

(4)confirm機制投遞訊息的高延遲性

(5)高併發下如何投遞訊息才能不丟失

(6)訊息中介軟體全鏈路100%資料不丟失能做到嗎?

1

前情提示

這篇文章,我們轉移到訊息中介軟體的生產端,一起來看看如何保證投遞到MQ的資料不丟失。

如果投遞出去的訊息在網路傳輸過程中丟失,或者在RabbitMQ的記憶體中還沒寫入磁碟的時候當機,都會導致生產端投遞到MQ的資料丟失。

而且丟失之後,生產端自己還感知不到,同時還沒辦法來補救。

下面的圖就展示了這個問題。

高併發場景下,如何保證生產者投遞到訊息中介軟體的訊息不丟失?

所以本文呢,我們就來逐步分析一下。


2

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

其實要解決這個問題,相信大家看過之前的消費端ack機制之後,也都猜到了。

很簡單,就是生產端(比如上圖的訂單服務)首先需要開啟一個confirm模式,接著投遞到MQ的訊息,如果MQ一旦將訊息持久化到磁碟之後,必須也要回傳一個confirm訊息給生產端。

這樣的話,如果生產端的服務接收到了這個confirm訊息,就知道是已經持久化到磁碟了。

否則如果沒有接收到confirm訊息,那麼就說明這條訊息半路可能丟失了,此時你就可以重新投遞訊息到MQ去,確保訊息不要丟失。

而且一旦你開啟了confirm模式之後,每次訊息投遞也同樣是有一個delivery tag的,也是起到唯一標識一次訊息投遞的作用。

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

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

或者另一種情況,如果某條訊息很長時間都沒給你回傳ack/nack,那可能是極端意外情況發生了,資料也丟了,你也可以自己重新投遞訊息到MQ去。

透過這套confirm機制,就可以實現生產端投遞訊息不會丟失的效果。大家來看看下面的圖,一起來感受一下。

高併發場景下,如何保證生產者投遞到訊息中介軟體的訊息不丟失?

3

confirm機制的程式碼實現

下面,我們再來看看confirm機制的程式碼實現:

高併發場景下,如何保證生產者投遞到訊息中介軟體的訊息不丟失?

4

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

這裡有一個很關鍵的點,就是一旦啟用了confirm機制投遞訊息到MQ之後,MQ是不保證什麼時候會給你一個ack或者nack的。

因為RabbitMQ自己內部將訊息持久化到磁碟,本身就是透過非同步批次的方式來進行的。

正常情況下,你投遞到RabbitMQ的訊息都會先駐留在記憶體裡,然後過了幾百毫秒的延遲時間之後,再一次性批次把多條訊息持久化到磁碟裡去。

這樣做,是為了兼顧高併發寫入的吞吐量和效能的,因為要是你來一條訊息就寫一次磁碟,那麼效能會很差,每次寫磁碟都是一次fsync強制刷入磁碟的操作,是很耗時的。

所以正是因為這個原因,你開啟了confirm模式之後,很可能你投遞出去一條訊息,要間隔幾百毫秒之後,MQ才會把訊息寫入磁碟,接著你才會收到MQ回傳過來的ack訊息,這個就是所謂confirm機制投遞訊息的高延遲性

大家看看下面的圖,一起來感受一下。

高併發場景下,如何保證生產者投遞到訊息中介軟體的訊息不丟失?

5

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


大家可以考慮一下,在生產端高併發寫入MQ的場景下,你會面臨兩個問題:

  • 1、你每次寫一條訊息到MQ,為了等待這條訊息的ack,必須把訊息儲存到一個儲存裡。

並且這個儲存不建議是記憶體,因為高併發下訊息是很多的,每秒可能都幾千甚至上萬的訊息投遞出去,訊息的ack要等幾百毫秒的話,放記憶體可能有記憶體溢位的風險。

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

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

首先,用來臨時存放未ack訊息的儲存需要承載高併發寫入,而且我們不需要什麼複雜的運算操作,這種儲存首選絕對不是MySQL之類的資料庫,而建議採用kv儲存。kv儲存承載高併發能力極強,而且kv操作效能很高。

其次,投遞訊息之後等待ack的過程必須是非同步的,也就是類似上面那樣的程式碼,已經給出了一個初步的非同步回撥的方式。

訊息投遞出去之後,這個投遞的執行緒其實就可以返回了,至於每個訊息的非同步回撥,是透過在channel註冊一個confirm監聽器實現的。

收到一個訊息ack之後,就從kv儲存中刪除這條臨時訊息;收到一個訊息nack之後,就從kv儲存提取這條訊息然後重新投遞一次即可;也可以自己對kv儲存裡的訊息做監控,如果超過一定時長沒收到ack,就主動重發訊息。

大家看看下面的圖,一起來體會一下:

高併發場景下,如何保證生產者投遞到訊息中介軟體的訊息不丟失?

6

訊息中介軟體全鏈路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%的可能性資料不丟失,但是還是有千萬分之一的機率會丟失。

當然,從實際的情況來說,能做到這種地步,其實基本上已經基本資料不會丟失了

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901780/viewspace-2564788/,如需轉載,請註明出處,否則將追究法律責任。

相關文章