一:前言概述
生產者生產消息到消費者消息消費,中間需要生產者將消息發送到交換器,再由交換器路由到隊列存儲,然後消費者進行消息消費。在沒有任何設置情況下,中間可能存在以下幾種情況導致消息丟失:
- 消費者將消息發送到交換器因爲RabbitMQ內部原因丟失消息
- 交換器將消息路由到隊列,因爲隊列不存在等因素導致消息丟失
- 隊列中存儲的消息在消費者未消費時RabbitMQ服務宕機導致消息丟失
- 消費者消費消息時消費者宕機未處理完消息導致消息丟失
針對上述情況,本文將根據每個節點講述如何操作確保消息投遞的可靠性,同時在保障可靠性的情況下可能會引發系列如消息重複等問題,也是本文將會涉及到的重點
二:事務TX
理解數據庫的事務就是將多次操作原子化,統一提交回滾實現數據一致性。RabbitMQ事務可能與之前數據庫等事務有一定差別,當然也是支持消息的提交與回滾操作。事務是解決生產者發送消息到交換器消息丟失問題的方案之一
2.1 相關操作
RabbitMQ中的事務實現有如下兩個個主要步驟:
- 將通信的信道設置爲事務模式
- 事務提交/事務回滾
2.2 代碼示例
// 將信道設置爲事務模式
channel.txSelect();
// 發送消息
try {
channel.basicPublish(bindingKey, bindingKey, false, false, null, messgae.getBytes("UTF-8"));
// 提交事務
channel.txCommit();
}catch (Exception e){
// 異常回滾事務,發生異常的時候可以嘗試重發或者記錄等操作
channel.txRollback();
}
2.3 總結
事務機制的確認需要等待上一條消息發送完畢反饋之後才能進行第二條消息發送,這樣的操作將會對於使用RabbitMQ而言感覺就是暴殄天物。如果是想要發送多條消息只能循環操作,但是注意如果沒有將channel信道設置爲事務模式不能進行事務操作
,不然會拋出異常。最後一點就是信道設置爲事務模式只需要操作一次即可
三:確認Confirm
事務機制對於RabbitMQ性能消耗是災難性的,針對生產者到交換器消息丟失處理提出了全新的輕量級處理方式。發送發確認機制confirm,該操作編碼上會有很多地方都在講什麼等待確認、批量確認、異步確認。一切爲了生產,所以本文將只會介紹生產用的異步確認方式
3.1 相關操作
RabbitMQ的生產發送確認實現由以下三個部分構成:
- 將信道channel設置爲確認模式
- 增加確認監聽Listener
- 處理監聽結果
3.2 代碼示例
- 每個通道發送到RabbitMQ的Broker中都會有唯一的編碼
- 生產端最好使用有序隊列存儲發送的消息,方便確認後的刪除
- 創建Channel通道時可以指定唯一編碼,標識該通道
// 設置confirm消息發送確認機制
channel.confirmSelect();
// 增加確認機制監聽器
channel.addConfirmListener(new ConfirmListener() {
/**
* 成功確認
* deliveryTag 表示消息的唯一標識
* multiple 本次確認是否爲批量操作
*/
@Override
public void handleAck (long deliveryTag, boolean multiple) throws IOException {
// 刪除有序集合中的消息
if (! multiple){
// 根據座標刪除消息
}else {
// 批量刪除消息
}
}
/**
* 失敗確認
* deliveryTag 表示消息的唯一標識
* multiple 本次確認是否爲批量操作
*/
@Override
public void handleNack (long deliveryTag, boolean multiple) throws IOException {
// 可以根據deliveryTag做重試操作等
}
});
3.3 注意事項
- 共存:事務與確認機制不能共存,不然會異常
- 查驗:通過RabbitMQ可視化監控界面可看到Channels欄Mod屬性T表示事務,C表示監聽確認
- 有序:因爲RabbitMQ生成的序列deliveryTag是由小到大自動遞增的,所以最好存儲消息的時候考慮到順序性,更方便通過deliveryTag定位到消息進行操作
四:Mandatory
交換器不存儲消息,所有消息都要路由到隊列存儲。如果中間過程消息丟失,對於生產者而言不設置的情況下是無法知曉的錯誤。Mandatory實現與Confirm實現類似,通過增加監控監聽Listener實現。前面文章消息與隊列進階詳細描述過這個參數監聽
4.1 相關操作
當消息到達交換器,但是沒有匹配隊列路由存儲時。若通過Mandatory實現監聽處理則需要如下幾個處理過程:
- 發送消息basicPublish()時將設置mandatory參數爲true
- 爲信道channel增加MandatoryListener監聽
4.2 代碼示例
- 首先可以看到因爲bindingKey與routingKey不一致,消息不能路由到隊列
- 然後可以看到發送消息時將mandatory參數設置爲true表示增加mandatory監聽
- 最後可以看到在信道上增加了ReturnLisrtener監聽,取得未路由消息相關參數信息
4.3 參數詳解
ReturnListener中僅僅包含唯一方法handleReturn(),該方法中含有系列參數,參數含義如下表所示:
參數 | 描述 |
---|---|
replyCode | 表示本次返回消息原因編碼,如312消息未路由 |
replyText | 表示本次返回消息原因描述 |
exchange | 監聽器接收到返回消息的交換器 |
routingKey | 本次消息發送的路由鍵 |
properties | 本次監聽到返回消息的屬性設置 |
body | 監聽到返回消息的消息體 |
五:備用交換器
Mandatory增加的ReturnListener監聽需要在發送消息代碼中增加邏輯,這對於追求功能專一性而言不是好消息。通過RabbitMQ也根本檢測不到這段邏輯,也不利於後續代碼維護。所以提出備用交換器,創建交換器時綁定,當交換器消息未找到路由隊列時消息將轉發到備用交換器
5.1 相關操作
備用交換器其原理類似於交換器與交換器綁定,需要注意以下幾點:
- 創建交換器時使用Map參數綁定備用交換器
- 備用交換器接收路由到的消息不會更改任何屬性,包括routingKey
- 可以將備用交換器設置爲內置交換器
5.2 代碼示例
通過參數Map綁定備用交換器,驗證效果將消息發送路由鍵routingKey設置爲備用交換器路由鍵。可以查看備用交換器創建時的第五個參數,上面也提到最優設置爲內置交換器,屬性internal
5.3 測試結果
最後顯示備用交換器中有一條消息,證明結果的正確性。備用交換器可以用作消息不能正確路由時的一種解決方案
六:隊列與消息持久化
這個問題在前面相關隊列與隊列消息的文章中已經詳細講解,爲了整個消息投遞可靠性的完整,這裏再次描述一下隊列與隊列消息的持久化。注意以點:
單獨的隊列消息持久化並不能實現消息持久化,同理單獨的隊列持久化也不能實現消息持久化。需要隊列與隊列消息同時持久化方可
6.1 隊列持久化
持久化即將隊列信息寫入磁盤持久化保存,當RabbitMQ應用服務故障宕機重啓時可以自動進行數據恢復的操作稱之爲隊列持久化。實現只需要在創建隊列時將持久化參數設置爲true即可,如下所示:
進入RabbitMQ應用的WEB頁面控制檯查看該隊列標誌D,表示持久化。如下所示:
6.2 隊列消息持久化
將RabbitMQ服務應用重啓,發現隊列恢復,但是隊列中消息數據並未恢復。因爲隊列消息持久化需要在發送消息時進行設置,不然也不會寫入磁盤保存。代碼如下所示:
- 發送消息方法參數列表要求傳遞BasicProperties,該類使用建造者模式設計,其中deliveryMod表示消息持久化。1 默認值不進行持久化,2 將消息持久化寫入磁盤
- MessageProperties類封裝常用系列BasicProperties對象,可以直接使用
6.3 總結
隊列持久化 + 隊列消息持久化 = 完整持久化,持久化對RabbitMQ應用的性能是一種負擔,可以根據數據類型進行範圍數據持久化。如訂單數據、支付數據等等較爲重要的數據可以採用持久化的操作儘量避免消息丟失
七:消費者確認
生產者消息已經投遞並路由到隊列存儲,當消費者消費時消費應用宕機導致消費邏輯不完整的宕機也是保證消息百分百投遞消費的關鍵一環。RabbitMQ針對這一點提供消費者確認機制,配置該特性後,當且僅當消費者確認以後RabbitMQ應用纔會刪除消息
講解RabbitMQ消費者確認機制前需要確認默認情況下消費者將自動確認,也就是當消息從RabbitMQ應用服務取出時將被刪除,這也是誘發消息丟失的原因。所以爲了實現後續手動控制消息確認的邏輯,消費消息時就需要將參數autoAck設置爲false
7.1 basicAck
消息確認,參數包含deliveryTag、multiple。作用與生產者確認Confirm一致:
- deliveryTag:RabbitMQ應用會爲每條消息產生唯一編號,生產者亦或是消費者都需要根據編碼進行相關消息操作
- multiple:批量操作,即將編碼小於本次操作編碼的消息都進行本次一致的操作
7.2 消息拒絕
消息拒絕有兩個API,basicReject()與basicNack(),兩者唯一的差距在於前者不能進行multiple的批量操作。兩者共同含有以下兩個參數屬性:
- deliveryTag:RabbitMQ應用會爲每條消息產生唯一編號,生產者亦或是消費者都需要根據編碼進行相關消息操作
- requeue:是否重新放回隊列,這裏拋棄的消息如果設置了死信轉發,將會被路由到配置的死信交換器