RabbitMQ 之訊息的可靠性消費
保障訊息的可靠性消費主要有以下兩個方面到內容
- 訊息在被消費端正確消費之前,不能刪除
- 訊息在被消費端正確消費之後,必須要刪除,否則訊息會被重複消費
什麼叫正確消費
消費端 消費訊息可以簡單看成兩個過程
- 接收訊息
- 消費訊息
接收到訊息後,是不能當作正確消費的,只有當訊息被業務處理完成之後,才能看作正確消費。注意,如果業務處理過程中程式奔潰、異常,也不能看作正確消費
訊息消費發生在消費端,RabbitMQ 怎麼知道訊息有沒有正確消費呢?
答案是通過 RabbitMQ 提供訊息確認機制(message acknowledgment)。
訊息確認機制
消費端在宣告佇列時,可以指定noAck引數,當noAck=true時,消費端收到訊息後會自動返回 ack 。當noAck=false時,消費端收到訊息後需要顯式呼叫basicAck,返回確認。
RabbitMQ 會一直持有訊息直到消費者返回 ack 為止,前提是沒有斷開連結(這樣設計的原因是,RabbitMQ 允許一個訊息被消費很長時間),一但連結斷開了,RabbitMQ 會認為訊息消費失敗,然後將訊息投遞給下一個消費者。
RabbitMQ 收到 ack訊號後會從記憶體(和磁碟,如果是持久化訊息的話)中移去訊息
在 spring 中的使用
在 spring 中使用訊息確認機制非常簡單,首先要配置 ack 的方式
application.properties
# 簽收方式 auth:自動簽收 manual:手動簽收 NONE:不簽收 推薦手動簽收
spring.rabbitmq.listener.simple.acknowledge-mode=manual
複製程式碼
如果不配置,預設為自動簽收,這種模式下,只要收到訊息就 ack 不管是否正確消費。這種模式顯然不是我們想要的,所以一般需要配置為手動簽收
在正確消費後,手動 ack
// todo 正確消費訊息
//手動 ack
channel.basicAck((Long)headers.get(AmqpHeaders.DELIVERY_TAG),false);
複製程式碼
重複消費
上面說到 一但消費端和 RabbitMQ 之間的連結斷開,RabbitMQ 會認為訊息消費失敗,然後將訊息投遞給下一個消費者。
但是,訊息真的消費失敗了嗎?不一定。舉個例子,訊息消費成功後,剛想手動 ack ,突然服務崩潰了。這個時候連結雖然斷開了,但是訊息已經消費成功了
重複消費就不可避免
後果
根據業務的不同,重複消費的後果的嚴重性也不同。
比如支付業務,訊息傳送端投遞了一個支付訊息給 RabbitMQ ,RabbitMQ 將支付訊息傳送給力 消費者a,消費者收到訊息後進行支付操作,支援完成之後,剛想手動 ack ,突然服務崩潰了,RabbitMQ 有將訊息投遞給了消費者B,消費者B又進行支付操作。
這樣以來就支付了兩次,要是真出現這種問題,哪個使用者敢用這樣的產品。
解決辦法
重複消費的解決辦法有兩種
- 訊息去重
- 冪等處理
當然,如果一個訊息被消費多次也不會產生嚴重後果也可以選擇不解決重複消費都問題
訊息去重
訊息去重是在訊息消費之前,先檢視是否消費過這個訊息。
通常做法是獲取業務訊息中的業務id,比如支付業務發來的支付訊息,裡面一般都包含支付id,這個id在支付業務系統中是唯一的。
處理完訊息之後,我們將這個支付id存下來(比如存到redis中),下次再有支付訊息過來,我們取出id 一查詢就知道之前有沒有消費過。
這種做法有很多缺點,比如需要訊息中包含唯一業務id(有的訊息可能沒有)、每個消費過的id都要存下來,而且還不能刪,時間一長會積攢很多等等。
當然你也可以選擇其他訊息去重方案,如果沒有合適的方案也可以選擇對消費訊息做冪等處理
冪等處理
所謂的對消費訊息做冪等處理,指定是一個訊息不管被重複消費多少次,結果都相當於只消費了一次。
這個就需要根據不同都業務進行巧妙都設計了,沒有統一都答案。