上一篇 RabbitMQ 入門之基礎概念 介紹了 RabbitMQ 的一些基礎概念,本文再來介紹其中的一些細節和其它的進階的概念。react
RabbitMQ 提供了消息在傳遞過程當中沒法發送到一個隊列(好比根據本身的類型和路由鍵沒有找到匹配的隊列)時將消息回傳給消息發送方的功能,使用 RabbitMQ 的客戶端提供 channel.basicPublish
方法的兩個參數 mandatory
和 immediate
(RabbitMQ 3.0 如下版本),除此以外還提供了一個備份交換器能夠將沒法發送的消息存儲起來處理,不用從新傳回給發送方。編程
mandatory 被定義在 RabbitMQ 提供的客戶端的 channel.basicPublish
方法中,以下所示:segmentfault
當把方法的 mandatory 參數設置爲 true
時,那麼會在交換器沒法根據自身的類型和路由鍵找到一個符合要求的隊列時,RabbitMQ 會自動調用 Basic.Return
把該消息回傳給發送方也就是咱們的消息生產者。反之,若是設置爲 false
的話,消息就會被直接丟棄掉。那麼問題來了,咱們要如何去獲取這些沒有被髮送出去的消息呢?RabbitMQ 給咱們提供了事件監聽機制來獲取這種消息,能夠經過 addReturnListener
方法添加一個 ReturnListener
來獲取這種未發送到隊列的消息,以下所示:優化
經過查看 ReturnListener 接口的源碼能夠看到,該接口只有一個方法,若是是 JDK8+ 的版本的話可使用 Lambda 表達式來簡化一些代碼。this
能夠看出,當設置了 mandatory 參數時,還必須爲生產者同時添加 ReturnListener 監聽器的編程邏輯,這樣就會使得生產者的代碼變得更加複雜了,爲了處理這種狀況,RabbitMQ 提供了 `備份交換器` 來將沒有成功路由出去的消息存儲起來,當咱們須要的時候再去處理便可。編碼
該的參數一樣也是在channel.basicPublish
方法中定義的,其官方描述以下:spa
This flag tells the server how to react if the message cannot be routed to a queue consumer immediately. If this flag is set, the server will return an undeliverable message with a Return method. If this flag is zero, the server will queue the message, but with no guarantee that it will ever be consumed.
當把 immediate 參數設置爲 true 時,若是交換器根據其類型和路由鍵找到符合要求的隊列時,發現全部隊列上沒有任何消費者,則該消息並不會存入到隊列中,會經過 Basic.Return 命令把消息回傳給生產者。簡而言之也就是說,當設置了 immediate 參數時,該消息關聯的隊列上存在消費者時,會當即發送消息到該隊列中,反之若是匹配的隊列上不存在任何消費者,則直接把消息回傳給生產者。這裏有一點須要注意的是:從 RabbitMQ 3.0 + 已經去除了該參數。code
TTL 是 time to live 首字母的簡稱,RabbitMQ 中能夠設置消息和隊列的過時時間,咱們先來看看要如何設置消息的過時時間。cdn
RabbitMQ 提供了兩種設置消息的過時時間,第一種是經過隊列的屬性設置,該方式的特色就是隊列中全部消息的過時時間都一致。還有一種是更小粒度的設置,就是對每條消息單獨設置過時時間,這種方式更加靈活,每條消息的過時時間均可以不同。這是你可能會問,若是同時設置了隊列的過時屬性和消息自己的過時屬性,最終以哪一個爲準呢?結果是 RabbitMQ 會比較這兩個 TTL 的值大小,以較小的那個爲準。很容易想到,經過隊列的屬性的方式設置過時時間的話是在聲明隊列的時候指定,對應到客戶端就是其提供的 channel.queueDeclare
方法的參數 arguments 指定,示例代碼以下:server
須要注意的是 x-message-ttl
參數的單位是毫秒。若是不設置 TLL,則表示該消息不會過時,若是將 TTL 設置爲 0,表示除非此時能夠把消息直接發送投遞到消費者端去,不然就會直接丟棄該消息。
準對每條消息設置 TTL 的方法是在發送消息的時候設置的,對應到客戶端方法是 channel.basicPublish
的 expiration 屬性參數,具體設置代碼以下:
這種設置方式,即便隊列過時也不會當即從隊列中移除,由於每條消息是否過時的斷定是在發送到消費者是才進行的,若是此時發現已通過期纔會刪除消息。而對於第一種方式則會把已通過期的消息移到隊列頭部,而後 RabbitMQ 只要按期的從頭開始掃描是否存在過時的消息便可。
設置隊列的過時時間使用的是客戶端的 channel.queueDeclare
方法參數中的 x-expires
參數,其單位一樣也是毫秒,不過須要注意的是它不能設置爲 0。設置隊列過時的代碼以下所示:
上面代碼建立了一個過時時間爲 15 分鐘的隊列。
死信交換器(DLX)的全稱是 Dead-Letter-Exchange
,也稱之爲死信郵箱。簡單來講就是當一個消息因爲 消息被拒絕
、 消息過時
、 隊列達到最大長度
時,變成死信(dead message)以後,會被從新發送到一個交換器中,這個交換器就是死信交換器,綁定在這個交換器上的隊列就稱之爲死信隊列。死信交換器實際上就是日常的交換器,能夠在任何隊列上指定,當在一個隊列上設置死信交換器後,若是該隊列出現死信時就會被 RabbitMQ 把死信消息從新發送到死信交換器上去,而後路由到死信隊列中,咱們能夠監聽這個隊列來處理那些死信消息。爲一個隊列設置死信交換器是在生產者的聲明隊列的方法中設置 x-dead-letter-message
參數來實現的,以下所示:
同時也能夠經過 x-dead-letter-routing-key
參數設置死信交互器的路由鍵,不設置默認使用原始度列的路由鍵。能夠到 RabbitMQ 的後臺管理界面,有 DLX
標誌的就是死信隊列。
RabbitMQ 提供的 DLX 是個比較實用的功能特性,它能夠在咱們消息不能被消費者正確消費的狀況下放入到死信隊列,後續咱們能夠經過這個死信隊列的內容來查看異常狀況來改造和優化系統。
顧名思義,延遲隊列存儲的是哪些須要等待指定時間後才能拿到的延遲消息,一個比較典型的場景就是訂單 30 分鐘後未支付取消訂單。這裏須要注意的是,在 RabbitMQ 中並無直接提供延遲隊列的功能,而是須要經過上面介紹的過時時間(TTL)和死信隊列一塊兒來實現,好比超時取消訂單這個場景,咱們可讓消費者訂閱死信隊列,設置正常的那個隊列的超時時間爲 30 分鐘並綁定到該死信隊列上,當消息超過 30 分鐘未被處理後消息就會把發送到死信隊列中,而後死信隊列的消費者就能夠在 30 分鐘後成功的消費到該消息了。
同時當咱們有其它的超時配置需求時也很方便擴展,好比能夠在生產者發送消息的時候經過設置不一樣的路由鍵,經過路由鍵來將消息發送到與交換器綁定的不一樣隊列中,而後這些隊列分別設置不一樣的過時時間和與之相對應的死信隊列,當消息過時時就會被 RabbitMQ 轉發到相應的死信隊列中,這樣就能夠去訂閱相應的死信隊列便可。
持久化能夠提升可靠性,能夠防止宕機或者重啓等異常下數據的丟失,RabbitMQ 的持久化從組成結構上能夠分爲三個部分,即交換器持久化、消息持久化和隊列持久化。
交換器持久化是在聲明交換器時將 durable 參數設置爲 true 來實現的。若是不設置持久化屬性的話,當 RabbitMQ 服務重啓後交換器的數據就會丟失,須要注意的是,是交換器的數據丟失,消息不會丟失,只是不能將消息發送到這個交換器中了,通常生產環境使用都會把該屬性設置爲持久化。
交換器的持久化僅僅只是保證了交換器自己的元數據不會丟失,沒法保證其存儲的消息不會丟失,若是須要其內部存儲的消息不丟失,則須要設置消息的持久化,經過將消息的投遞模式(deliveryMode)設置爲 2 便可實現消息的持久化,以下所示:
須要消息持久化的前提是其所在的隊列也要設置持久化,假如僅僅只設置消息的持久化的話,RabbitMQ 重啓以後隊列消失,而後消息也會丟失。這裏有點須要注意一下,雖然持久化能夠提升可靠性,可是持久化是將數據存儲到硬盤上,比直接操做內存要慢不少,因此對於哪些可靠性要求不高的業務不須要進行持久化。
隊列的持久化的設置和交換器持久化相似,一樣也是在聲明的時候經過 durable 參數設置爲 true 實現的,若是不設置,當 RabbitMQ 重啓後,相關的隊列元數據也會丟失,相應的其存儲的消息也會隨之丟失掉。
將交換器、隊列、消息都設置了持久化以後就能百分之百保證數據不丟失了嗎?其實沒法保證百分之百數據不丟失。好比消費者在訂閱消費隊列時將自動應答(autoAck)參數設置爲 true 的話,在接收到消息後還沒來得及處理就掛了,這時須要把自動應答設置 false,進行手動 ack 應答便可。還有一個就是因爲不是實時持久化存盤,當消息存盤的過程當中 RabbitMQ 宕機了,此時也會發生數據丟失,此時須要經過 RabbitMQ 的 鏡像隊列機制
來處理了。
本文主要介紹了一些參數具體使用時的設置細節和死信隊列、延遲隊列以及持久化等,還有一些比較重要的點沒有涉及到,好比消息確認機制。「紙上得來終覺淺,絕知此事要躬行」,在瞭解一些基礎的概念以後仍是須要經過具體編碼實踐才能對其更加理解深入。