保障消息的可靠性消費主要有如下兩個方面到內容redis
消費端 消費消息能夠簡單當作兩個過程spring
接收到消息後,是不能看成正確消費的,只有當消息被業務處理完成以後,才能看做正確消費。注意,若是業務處理過程當中程序奔潰、異常,也不能看做正確消費bash
消息消費發生在消費端,RabbitMQ 怎麼知道消息有沒有正確消費呢?app
答案是經過 RabbitMQ 提供消息確認機制(message acknowledgment)。post
消費端在聲明隊列時,能夠指定noAck參數,當noAck=true時,消費端收到消息後會自動返回 ack 。當noAck=false時,消費端收到消息後須要顯式調用basicAck,返回確認。spa
RabbitMQ 會一直持有消息直到消費者返回 ack 爲止,前提是沒有斷開連接(這樣設計的緣由是,RabbitMQ 容許一個消息被消費很長時間),一但連接斷開了,RabbitMQ 會認爲消息消費失敗,而後將消息投遞給下一個消費者。設計
RabbitMQ 收到 ack信號後會從內存(和磁盤,若是是持久化消息的話)中移去消息code
在 spring 中使用消息確認機制很是簡單,首先要配置 ack 的方式rabbitmq
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 ,RabbitMQ 將支付消息發送給力 消費者a,消費者收到消息後進行支付操做,支持完成以後,剛想手動 ack ,忽然服務崩潰了,RabbitMQ 有將消息投遞給了消費者B,消費者B又進行支付操做。
這樣以來就支付了兩次,要是真出現這種問題,哪一個用戶敢用這樣的產品。
重複消費的解決辦法有兩種
固然,若是一個消息被消費屢次也不會產生嚴重後果也能夠選擇不解決重複消費都問題
消息去重是在消息消費以前,先查看是否消費過這個消息。
一般作法是獲取業務消息中的業務id,好比支付業務發來的支付消息,裏面通常都包含支付id,這個id在支付業務系統中是惟一的。
處理完消息以後,咱們將這個支付id存下來(好比存到redis中),下次再有支付消息過來,咱們取出id 一查找就知道以前有沒有消費過。
這種作法有不少缺點,好比須要消息中包含惟一業務id(有的消息可能沒有)、每一個消費過的id都要存下來,並且還不能刪,時間一長會積攢不少等等。
固然你也能夠選擇其餘消息去重方案,若是沒有合適的方案也能夠選擇對消費消息作冪等處理
所謂的對消費消息作冪等處理,指定是一個消息無論被重複消費多少次,結果都至關於只消費了一次。
這個就須要根據不一樣都業務進行巧妙都設計了,沒有統一都答案。