RabbitMQ學習筆記之進階篇

消息何去何從

mandatory參數

生產者發送消息時設置的參數。爲true時,當交換器無法根據自身的類型和路由鍵找到一個符合條件的隊列,RabbitMQ會把該消息退回給生產者;爲false時,出現上述情形,消息會直接被丟棄。

immediate參數

生產者發送消息時設置的參數。爲true時,如果交換器將消息路由到隊列時發現隊列上並不存在任何消費者,那麼這條消息將不會存入隊列中。當與路由鍵匹配的所有隊列都沒有消費者時,該消息會返回給生產者。
概括來說,mandatory參數告訴Broker至少將該消息路由到一個隊列中,否則將消息返回給生產者。immediate參數告訴Broker,如果該消息關聯的隊列上有消費者,則立刻投遞;如果所有匹配的隊列上都沒有消費者,則直接把消息返還給生產者。RabbitMQ 3.0版本開始去掉對該參數的支持,官方說法是它影響鏡像隊列的性能,增加代碼複雜性。

備份交換器

英文名Alternate Exchange,簡稱AE。申明交換器的時候,可以爲該交換器設置備份交換器,當發送到該交換器的消息無法路由到任何隊列時,就會發送給備份交換器,備份交換器設置爲fanout類型比較合適。對於備份交換器,總結以下幾種特殊情況:

  1. 如果設置的備份交換器不存在,客戶端和RabbitMQ服務端不會有異常出現,此時消息會丟失;
  2. 如果備份交換器沒有綁定任何隊列,客戶端和RabbitMQ服務端不會有異常出現,此時消息會丟失;
  3. 如果消息通過備份交換器沒有路由到任何隊列,客戶端和RabbitMQ服務端不會有異常出現,此時消息會丟失;
  4. 如果備份交換器和mandatory參數一起使用,那麼mandatory參數無效。

過期時間(TTL,Time To Live)

有2種方法可以設置消息的TTL,第一種是通過隊列的屬性設置,隊列中所有消息都有相同的過期時間;第二種是對消息本身進行單獨設置,每條消息的TTL可以不同。如果兩者同時使用,則消息的過期時間以兩者之中的較小的那個爲準。消息在隊列中的生存時間一旦超過TTL值時,就會變成“死信”(Dead Message)。如果不設置TTL,則表示此消息不會過期;如果將TTL設置爲0,則表示除非此時可以直接將消息投遞給消費者,否則該消息會立即被丟棄。

死信隊列

當消息在一個隊列中變成死信之後,它能被重新發送到另外一個交換器中,這個交換器叫DLX(Dead Letter Exchange),綁定DLX的隊列就稱爲死信隊列,可以監聽該隊列中的消息以進行相應的處理,這個特性與將消息的TTL設置爲0配合使用可以彌補immediate參數的功能。消息變成死信一般有以下幾種情況:

  1. 消息被拒絕(Basic.Reject/Basic.Nack),並且設置requeue參數爲false;
  2. 消息過期;
  3. 隊列達到最大長度。

對於RabbitMQ來說,死信隊列是一個非常有用的特性,它可以處理異常情況下消息不能夠被消費者正確消費(消費者調用Basic.Reject/Basic.Nack)而置於死信隊列中的情況,後續分析程序可以通過消費這個死信隊列中的內容來分析當時所遇到的異常情況,進而可以改善和優化系統。

延遲隊列

延遲隊列存儲的是延遲消息,所謂延遲消息,就是不想讓消費者立馬拿到而是等待特定的時間後才讓消費者拿到的消息。RabbitMQ本身沒有直接支持延遲隊列的功能,但是可以通過前面介紹的DLX和TTL模擬出延遲隊列的功能。基本思路就是給某個隊列設置一個TTL值,即延遲時間,當該隊列的消息過期之後就進入到死信隊列,消費者消費死信隊列的消息。

RPC實現

RPC是Remote Procedure Call的簡稱,即遠程過程調用。它是一種通過網絡從遠程計算機上請求服務,而不需要了解底層網絡的技術。
通過RabbitMQ實現RPC很簡單,客戶端發送請求消息,服務端回覆響應的消息,爲了接收響應消息,我們需要在請求消息頭中帶上回調隊列。通過replyTo參數指定回調隊列,通過correlationId關聯請求和調用RPC之後的回覆。

持久化

持久化可以提高RabbitMQ的性能,以防在異常情況(重啓、關閉、宕機)下的數據丟失。RabbitMQ的持久化分爲三部分:交換器的持久化、隊列的持久化和消息的持久化。
交換器的持久化是通過在聲明隊列時將durable參數設置爲true實現的。如果交換器不設置持久化,那麼在RabbitMQ服務重啓之後,相關的交換器元數據會丟失,不過消息不會丟失,只是不能再將消息發送到這個交換器中了。
隊列的持久化是通過在聲明隊列時將durable參數設置爲true實現的。如果隊列不設置持久化,那麼在RabbitMQ服務重啓之後,相關隊列的元數據和消息都會丟失。
隊列的持久化能保證它本身的元數據不會因異常情況而丟失,但是不能保證它內部存儲的消息不會丟失,要確保消息不會丟失,需要將消息設置爲持久化。通過將消息的投遞模式(BasicProperties裏的deliveryMode屬性)設置爲2即可實現消息的持久化。設置了隊列和消息的持久化,當RabbitMQ服務重啓之後,消息依舊存在。可以將所有消息持久化,但是這樣會嚴重影響RabbitMQ的性能。
將交換器、隊列和消息都設置爲持久化就能百分之一百保證消息不丟失嗎?答案是否定的,具體原因這裏就不討論了。可以引入RabbitMQ的鏡像隊列機制來進一步保證消息不丟失,相當於配置了副本,主節點掛掉自動切換到從節點。

生產者確認

生產者將消息發出去之後,怎麼知道消息到底有沒有到達RabbitMQ服務器?針對這個問題,RabbitMQ提供了2種解決方案:

  1. 通過事務機制實現;
  2. 通過發送方確認機制實現。

事務機制

正常情況下是這樣四個步驟:

  1. 客戶端發送Tx.Select,將信道置爲事務模式;
  2. Broker回覆Tx.Select-Ok,確認已將信道置爲事務模式;
  3. 在發送完消息之後,客戶端發送Tx.Commit提交事務;
  4. Broker回覆Tx.Commit-Ok,確認事務已提交。

異常情況下進行事務回滾,步驟如下:

  1. 客戶端發送Tx.Select,將信道置爲事務模式;
  2. Broker回覆Tx.Select-Ok,確認已將信道置爲事務模式;
  3. 在發送完消息之後,客戶端出現異常,發送Tx.Rollback回滾事務;
  4. Broker回覆Tx.Rollback-Ok,確認事務已回滾。

發送方確認機制

事務機制會降低RabbitMQ的吞吐量,相比之下發送方確認機制比較輕量級。生產者將信道設置成confirm(確認)模式,一旦信道進入confirm模式,所有在該信道上面發佈的消息都會被指派一個唯一的ID(從1開始),一旦消息被投遞到所有匹配的隊列之後,RabbitMQ就會發送一個確認(Basic.Ack)給生產者(包含消息的唯一ID)。如果消息和隊列是可持久化的,那麼確認消息會在消息寫入磁盤之後發出。
事務機制在一條消息發送之後會使發送端阻塞,以等待RabbitMQ的迴應,之後才能繼續發送下一條消息。相比之下,發送方確認機制最大的好處在於它是異步的,一旦發佈一條消息,生產者應用程序就可以在等待信道返回確認的同時繼續發送下一條消息。

消費端要點介紹

消息分發

當隊列擁有多個消費者時,隊列收到的消息將以輪詢(round-robin)的分發方式發送給消費者。每條消息只會發送給訂閱列表裏的一個消費者。
假如每個消費者處理消息的能力不同,比如有的消費者機器配置比較好,有的差,那麼輪詢就不是那麼優雅。RabbitMQ可以限制信道上的消費者所能保持的最大未確認消息的數量,每發送一條信息都會爲對應的消費者計數,如果達到了所設定的上限,那麼RabbitMQ就不會向這個消費者再發送任何消息,直到消費者確認了某條消息之後,RabbitMQ將相應的計數減1,之後消費者可以繼續接收消息,直到再次達到計數上限。

消息順序性

一般消息中間件的消息傳輸保障分爲三個層級:

  1. At most once:最多一次。消息可能會丟失,但絕對不會重複傳輸;
  2. At least once:最少一次。消息絕對不會丟失,但可能會重複傳輸;
  3. Exactly once:恰好一次。每條消息肯定會被傳輸一次且僅傳輸一次。

RabbitMQ支持其中的“最多一次”和“最少一次”。其中“最少一次”投遞實現需要考慮以下這幾個方面的內容:

  1. 消息生產者需要開啓事務機制或者發送者確認機制,以確保消息可以可靠的傳輸到RabbitMQ中。
  2. 消息生產者需要配合使用mandatory參數或者備份交換器來確保消息能夠從交換器路由到隊列中,進而能夠保存下來而不被丟棄。
  3. 消息和隊列都需要進行持久化處理,以確保RabbitMQ服務器在遇到異常情況時不會造成消息丟失。
  4. 消費者在消費消息的同時需要將autoAck設置爲false,然後通過手動確認的方式去確認已經正確消費的消息,以避免在消費端引起不必要的消息丟失。

參考資料

  1. 朱忠華《RabbitMQ實戰指南》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章