若是投遞出去的消息在網絡傳輸過程當中丟失,或者在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的場景下,你會面臨兩個問題:
而且這個存儲不建議是內存,由於高併發下消息是不少的,每秒可能都幾千甚至上萬的消息投遞出去,消息的ack要等幾百毫秒的話,放內存可能有內存溢出的風險。
針對這兩個問題,相對應的方案其實也呼之欲出了。
首先,用來臨時存放未ack消息的存儲須要承載高併發寫入,並且咱們不須要什麼複雜的運算操做,這種存儲首選絕對不是MySQL之類的數據庫,而建議採用kv存儲。kv存儲承載高併發能力極強,並且kv操做性能很高。
其次,投遞消息以後等待ack的過程必須是異步的,也就是相似上面那樣的代碼,已經給出了一個初步的異步回調的方式。
消息投遞出去以後,這個投遞的線程其實就能夠返回了,至於每一個消息的異步回調,是經過在channel註冊一個confirm監聽器實現的。
收到一個消息ack以後,就從kv存儲中刪除這條臨時消息;收到一個消息nack以後,就從kv存儲提取這條消息而後從新投遞一次便可;也能夠本身對kv存儲裏的消息作監控,若是超過必定時長沒收到ack,就主動重發消息。
你們看看下面的圖,一塊兒來體會一下:
6
消息中間件全鏈路100%數據不丟失能作到嗎?
到此爲止,咱們已經把生產端和消費端如何保證消息不丟失的相關技術方案結合RabbitMQ這種中間件都給你們分析過了。
其實,架構思想是通用的, 不管你用的是哪種MQ中間件,他們提供的功能是不太同樣的,可是你都須要考慮以下幾點:
目前來講,咱們初步的藉着RabbitMQ舉例,已經把從前到後一整套技術方案的原理、設計和實現都給你們分析了一遍了。
可是此時真的能作到100%數據不丟失嗎?恐怕未必,你們再考慮一下個特殊的場景。
生產端投遞了消息到MQ,並且持久化到磁盤而且回傳ack給生產端了。
可是此時MQ還沒投遞消息給消費端,結果MQ部署的機器忽然宕機,並且由於未知的緣由磁盤損壞了,直接在物理層面致使MQ持久化到磁盤的數據找不回來了。
這個你們千萬別覺得是開玩笑的,你們若是留意留意行業新聞,這種磁盤損壞致使數據丟失的是真的有的。
那麼此時即便你把MQ重啓了,磁盤上的數據也丟失了,數據是否是仍是丟失了?
你說,我能夠用MQ的集羣機制啊,給一個數據作多個副本,好比後面咱們就會給你們分析RabbitMQ的鏡像集羣機制,確實能夠作到數據多副本。
可是即便數據多副本,必定能夠作到100%數據不丟失?
好比說你的機房忽然遇到地震,結果機房裏的機器所有沒了,數據是否是仍是全丟了?
說這個,並非說要擡槓。而是告訴你們,技術這個東西,100%都是理論上的指望。
應該說,咱們凡事都朝着100%去作,可是理論上是不可能徹底作到100%保證的,可能就是作到99.9999%的可能性數據不丟失,可是仍是有千萬分之一的機率會丟失。
固然,從實際的狀況來講,能作到這種地步,其實基本上已經基本數據不會丟失了。
若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:787707172,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。