mq消息丟失場景有哪些?web
如何避免消息丟失?編程
大廠如何解決這些問題的?服務器
mq消息丟失場景有哪些?網絡
首先咱們看下消息週期投遞過程:併發
解決RabbitMQ消息丟失問題和保證消息可靠性異步
咱們把該圖分三部分,左中右三部分,每部分都會致使消息丟失狀況:ide
1.生產者生產消息到RabbitMQ-Server 消息丟失場景高併發
外界環境問題致使:發生網絡丟包、網絡故障等形成消息丟失性能
代碼層面,配置層面,考慮不全致使消息丟失ui
發送端使用Confirm模式,方案不夠嚴謹,好比MQ Server接收消息失敗發送 nack給發送端後,發送端監聽失敗或者沒作任何事情,消息丟失的狀況;
再好比發送消息到exchange後,發下路由和queue沒有綁定,消息會存在丟失狀況,下面會講到具體的例子。
2.RabbitMQ-Server中存儲的消息丟失
消息沒有持久化致使丟失
單節點或者集羣模式沒有鏡像模式消息丟失
個別磁盤意外損害致使消息同步失敗
機房被炸
3.RabbitMQ-Server到消費者消息丟失
消費者接收到相關消息以後,還沒來得及處理就宕機了,消息丟失
如何避免消息丟失?
下面也是從三個方面介紹:
生產者生產消息到RabbitMQ-Server 可靠性保證
RabbitMQ-Server中存儲的消息如何保證
RabbitMQ-Server到消費者消息如何不丟
1. 生產者生產消息到RabbitMQ-Server可靠性保證
這個過程,消息可能會丟,好比發生網絡丟包、網絡故障等形成消息丟失,通常狀況下若是不採起措施,生產者沒法感知消息是否已經正確無誤的發送到exchange中,若是生產者能感知到的話,它能夠進行進一步的處理動做,好比從新投遞相關消息以確保消息的可靠性。
1.1 別擔憂,有一種方案能夠解決:就是 AMQP協議提供的一個事務機制
RabbitMQ客戶端中Channel 接口提供了幾個事務機制相關的方法: channel.txSelect channel.txCommit channel.txRollback 源碼截圖以下:com.rabbitmq.client 包中public interface Channel extendsShutdownNotifier {}接口
在生產者發送消息以前,經過channel.txSelect開啓一個事務,接着發送消息, 若是消息投遞server失敗,進行事務回滾channel.txRollback,而後從新發送, 若是server收到消息,就提交事務channel.txCommit可是,不多有人這麼幹,由於這是同步操做,一條消息發送以後會使發送端阻塞,以等待RabbitMQ-Server的迴應,以後才能繼續發送下一條消息,生產者生產消息的吞吐量和性能都會大大下降。
1.2 不過幸運的是RabbitMQ提供了一個改進方案,即發送方確認機制(publisher confirm)
首先生產者經過調用channel.confirmSelect方法將信道設置爲confirm模式,一旦信道進入confirm模式,全部在該信道上面發佈的消息都會被指派一個惟一的ID(從1開始),一旦消息被投遞到全部匹配的隊列以後,RabbitMQ就會發送一個確認(Basic.Ack)給生產者(包含消息的惟一deliveryTag和multiple參數),這就使得生產者知曉消息已經正確到達了目的地了。
其實Confirm模式有三種方式實現:
串行confirm模式:producer每發送一條消息後,調用waitForConfirms()方法,等待broker端confirm,若是服務器端返回false或者超時時間內未返回,客戶端進行消息重傳。
批量confirm模式:producer每發送一批消息後,調用waitForConfirms()方法,等待broker端confirm。
異步confirm模式:提供一個回調方法,broker confirm了一條或者多條消息後producer端會回調這個方法。 咱們分別來看看這三種confirm模式
串行confirm
批量confirm模式
上面代碼是簡單版本的,生產環境絕對不是循環發送的,而是根據業務狀況, 各個客戶端程序須要按期(每x秒)或定量(每x條)或者二者結合來pubish消息,而後等待服務器端confirm。相比普通confirm模式,批量能夠極大提高confirm效率。
可是有沒有發現什麼問題?
問題1: 批量發送的邏輯複雜話了。
問題2: 一旦出現confirm返回false或者超時的狀況時,客戶端須要將這一批次的消息所有重發,這會帶來明顯的重複消息數量,而且,當消息常常丟失時,批量confirm性能應該是不升反降的。
異步confirm模式
異步模式須要本身多寫一部分複雜的代碼實現,異步監聽類,監聽server端的通知消息,異步的好處性能會大幅度提高,發送完畢以後,能夠繼續發送其餘消息。 MQServer通知生產端ConfirmListener監聽類:用戶能夠繼承接口實現本身的實現類,處理消息確認機制,此處繼承類代碼省略,就是上面 ProxiedConfirmListener 類: 下面貼下要實現的接口:
上面的接口頗有意思,若是是你的話,怎麼實現? 消息投遞前如何存儲消息,ack 和 nack 如何處理消息?
下面看下異步confirm的消息投遞流程:
解決RabbitMQ消息丟失問題和保證消息可靠性
解釋下這張圖片:
channerl1 連續發類1,2,3條消息到RabbitMQ-Server,RabbitMQ-Server通知返回一條通知,裏面包含回傳給生產者的確認消息中的deliveryTag包含了確認消息的序號,此外還有一個參數multiple=true,表示到這個序號以前的全部消息都已經獲得了處理。這樣客戶端和服務端通知的次數就減小類,提高類性能。
加點消息存儲和刪除邏輯
事務機制和publisher confirm機制確保的是消息可以正確的發送至RabbitMQ,這裏的「發送至RabbitMQ」的含義是指消息被正確的發往至RabbitMQ的交換器,若是此交換器沒有匹配的隊列的話,那麼消息也將會丟失,怎麼辦?
這裏有兩個解決方案,
1. 使用mandatory 設置true
2. 利用備份交換機(alternate-exchange):實現沒有路由到隊列的消息
咱們看下RabbitMQ客戶端代碼方法
Channel 類中 發佈消息方法
void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body) throws IOException;
解釋下:basicPublish 方法中的,mandatory和immediate
/** * 當mandatory標誌位設置爲true時,若是exchange根據自身類型和消息routeKey沒法找到一個符合條件的queue, 那麼會調用basic.return方法將消息返回給生產者<br> * 當mandatory設置爲false時,出現上述情形broker會直接將消息扔掉。 */ @Setter(AccessLevel.PACKAGE) private boolean mandatory = false; /** * 當immediate標誌位設置爲true時,若是exchange在將消息路由到queue(s)時發現對於的queue上沒有消費者, 那麼這條消息不會放入隊列中。 當immediate標誌位設置爲false時,exchange路由的隊列沒有消費者時,該消息會經過basic.return方法返還給生產者。 * RabbitMQ 3.0版本開始去掉了對於immediate參數的支持,對此RabbitMQ官方解釋是:這個關鍵字違背了生產者和消費者之間解耦的特性,由於生產者不關心消息是否被消費者消費掉 */ @Setter(AccessLevel.PACKAGE) private boolean immediate;
因此爲了保證消息的可靠性,須要設置發送消息代碼邏輯。若是不單獨形式設置mandatory=false
使用mandatory 設置true的時候有個關鍵點要調整,生產者如何獲取到沒有被正確路由到合適隊列的消息呢?經過調用channel.addReturnListener來添加ReturnListener監聽器實現,只要發送的消息,沒有路由到具體的隊列,ReturnListener就會收到監聽消息。
channel.addReturnListener(new ReturnListener() { public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP .BasicProperties basicProperties, byte[] body) throws IOException { String message = new String(body); //進入該方法表示,沒路由到具體的隊列 //監聽到消息,能夠從新投遞或者其它方案來提升消息的可靠性。 System.out.println("Basic.Return返回的結果是:" + message); } });
此時有人問了,不想複雜化生產者的編程邏輯,又不想消息丟失,那麼怎麼辦? 還好RabbitMQ提供了一個叫作alternate-exchange東西,翻譯下就是備份交換器,這個幹什麼用呢?很簡單,它能夠將未被路由的消息存儲在另外一個exchange隊列中,再在須要的時候去處理這些消息。
那如何實現呢?
簡單一點能夠經過webui管理後臺設置,當你新建一個exchange業務的時候,能夠給它設置Arguments,這個參數就是 alternate-exchange,其實alternate-exchange就是一個普通的exchange,類型最好是fanout 方便管理
解決RabbitMQ消息丟失問題和保證消息可靠性
當你發送消息到你本身的exchange時候,對應key沒有路由到queue,就會自動轉移到alternate-exchange對應的queue,起碼消息不會丟失。
下面一張圖看下投遞過程:
解決RabbitMQ消息丟失問題和保證消息可靠性
那麼有人有個疑問,上面介紹了,兩種方式處理,發送的消息沒法路由到隊列的方案, 若是備份交換器和mandatory參數一塊兒使用,會有什麼效果?
答案是:mandatory參數無效
總結下上面內容,主要如何保證消息從生產者到RabbitMQ Server 端可靠性
1. Transaction: 消息落盤,只能同步開啓、提交及回滾。
2. Confirm:消息進入緩衝區,支持同步、異步、批量確認。
3. Transaction和publisher confirm機制二者是互斥的
4. 通常在生產者這塊避免數據丟失,都是用 Confirm 機制的。
2.RabbitMQ-Server中存儲的消息如何保證
通常消息都是存內存中的,若是消息沒有持久化硬盤,一天機器須要重啓,獲取意外停電,重啓機器後,消息全丟了,因此消息持久化是必備。