1. 程式人生 > >RabbitMQ 惰性佇列Lazy Queue

RabbitMQ 惰性佇列Lazy Queue

RabbitMQ 佇列分為幾種型別,按照不同維度來分,可以分為排他性佇列、普通佇列、延遲佇列、惰性佇列、釋出訂閱佇列等。

今天我們討論的主角是惰性佇列 Lazy Queue。眾所周知,佇列可以儲存訊息並實現訊息收發,這應該是訊息佇列中最重要的功能之一。

我們使用訊息佇列有幾個優勢,解耦、高效、發完不管、高可用。上一篇我們聊了RabbitMQ的映象佇列機制,映象佇列是高可用實現的一個有利保障,但在高可用的同時,必須提供高效的服務,才能被更多普通勞苦大眾所接受。

RabbitMQ提供高效服務的幾種途徑,設定RAM節點、自動同步,先記憶體後磁碟的讀取方式。在佇列設定持久化後,我們讀取佇列中的訊息,如果都是先從記憶體後從磁碟讀取,那麼無形會佔用更多系統資源,畢竟記憶體應該留給更多有需要的地方。

如果傳送端過快或消費端宕機,導致訊息大量積壓,此時訊息還是在記憶體和磁碟各儲存一份,在訊息大爆發的時候,MQ伺服器會撐不住,影響其他佇列的訊息收發,能不能有效的處理這種情況呢。答案 惰性佇列

RabbitMQ從3.6.0版本開始引入了惰性佇列(Lazy Queue)的概念。惰性佇列會盡可能的將訊息存入磁碟中,而在消費者消費到相應的訊息時才會被載入到記憶體中,它的一個重要的設計目標是能夠支援更長的佇列,即支援更多的訊息儲存。當消費者由於各種各樣的原因(比如消費者下線、宕機亦或者是由於維護而關閉等)而致使長時間內不能消費訊息造成堆積時,惰性佇列就很有必要了

預設情況下,當生產者將訊息傳送到RabbitMQ的時候,佇列中的訊息會盡可能的儲存在記憶體之中,這樣可以更加快速的將訊息傳送給消費者。即使是持久化的訊息,在被寫入磁碟的同時也會在記憶體中駐留一份備份。當RabbitMQ需要釋放記憶體的時候,會將記憶體中的訊息換頁至磁碟中,這個操作會耗費較長的時間,也會阻塞佇列的操作,進而無法接收新的訊息。雖然RabbitMQ的開發者們一直在升級相關的演算法,但是效果始終不太理想,尤其是在訊息量特別大的時候

惰性佇列會將接收到的訊息直接存入檔案系統中,而不管是持久化的或者是非持久化的,這樣可以減少了記憶體的消耗,但是會增加I/O的使用,如果訊息是持久化的,那麼這樣的I/O操作不可避免,惰性佇列和持久化訊息可謂是“最佳拍檔”。注意如果惰性佇列中儲存的是非持久化的訊息,記憶體的使用率會一直很穩定,但是重啟之後訊息一樣會丟失
 

佇列具備兩種模式:default和lazy。預設的為default模式,在3.6.0之前的版本無需做任何變更。lazy模式即為惰性佇列的模式,可以通過呼叫channel.queueDeclare方法的時候在引數中設定,也可以通過Policy的方式設定,如果一個佇列同時使用這兩種方式設定的話,那麼Policy的方式具備更高的優先順序。如果要通過宣告的方式改變已有佇列的模式的話,那麼只能先刪除佇列,然後再重新宣告一個新的
設定佇列為惰性佇列的命令:

rabbitmqctl set_policy Lazy "^myqueue$" '{"queue-mode":"lazy"}' --apply-to queues

惰性佇列和普通佇列相比,只有很小的記憶體開銷。這裡很難對每種情況給出一個具體的數值,但是我們可以類比一下:當傳送1千萬條訊息,每條訊息的大小為1KB,並且此時沒有任何的消費者,那麼普通佇列會消耗1.2GB的記憶體,而惰性佇列只消耗1.5MB的記憶體

據官網測試資料顯示,對於普通佇列,如果要傳送1千萬條訊息,需要耗費801秒,平均傳送速度約為13000條/秒。如果使用惰性佇列,那麼傳送同樣多的訊息時,耗時是421秒,平均傳送速度約為24000條/秒。出現效能偏差的原因是普通佇列會由於記憶體不足而不得不將訊息換頁至磁碟。如果有消費者消費時,惰性佇列會耗費將近40MB的空間來發送訊息,對於一個消費者的情況,平均的消費速度約為14000條/秒。

如果要將普通佇列轉變為惰性佇列,那麼我們需要忍受同樣的效能損耗。當轉變為惰性佇列的時候,首先需要將快取中的訊息換頁至磁碟中,然後才能接收新的訊息。反之,當將一個惰性佇列轉變為普通佇列的時候,和恢復一個佇列執行同樣的操作,會將磁碟中的訊息批量的匯入到記憶體中。
 

佇列引數的設定

 

 

  • Message TTL(x-message-ttl):設定佇列中的所有訊息的生存週期(統一為整個佇列的所有訊息設定生命週期), 也可以在釋出訊息的時候單獨為某個訊息指定剩餘生存時間,單位毫秒, 類似於redis中的ttl,生存時間到了,訊息會被從隊裡中刪除,注意是訊息被刪除,而不是佇列被刪除, 特性Features=TTL, 單獨為某條訊息設定過期時間AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties().builder().expiration(“6000”); 

  • Auto Expire(x-expires): 當佇列在指定的時間沒有被訪問(consume, basicGet, queueDeclare…)就會被刪除,Features=Exp

  • Max Length(x-max-length): 限定佇列的訊息的最大值長度,超過指定長度將會把最早的幾條刪除掉, 類似於mongodb中的固定集合,例如儲存最新的100條訊息, Feature=Lim

  • Max Length Bytes(x-max-length-bytes): 限定佇列最大佔用的空間大小, 一般受限於記憶體、磁碟的大小, Features=Lim B

  • Dead letter exchange(x-dead-letter-exchange): 當佇列訊息長度大於最大長度、或者過期的等,將從佇列中刪除的訊息推送到指定的交換機中去而不是丟棄掉,Features=DLX

  • Dead letter routing key(x-dead-letter-routing-key):將刪除的訊息推送到指定交換機的指定路由鍵的佇列中去, Feature=DLK

  • Maximum priority(x-max-priority):優先順序佇列,宣告佇列時先定義最大優先順序值(定義最大值一般不要太大),在釋出訊息的時候指定該訊息的優先順序, 優先順序更高(數值更大的)的訊息先被消費,

  • Lazy mode(x-queue-mode=lazy): Lazy Queues: 先將訊息儲存到磁碟上,不放在記憶體中,當消費者開始消費的時候才載入到記憶體中

  • Master locator(x-queue-master-locator)

總結:

惰性佇列的存在是為了減少記憶體佔用,避免由於記憶體不足而產生的換頁操作。

但對比惰性佇列和普通佇列,如果在記憶體和磁碟均不設限制的話,採用普通佇列的效率會更高,畢竟磁碟再快,對比記憶體也會差一個數量級。

但如果伺服器的配置捉襟見肘的話,可以考慮惰性佇列的存在,畢竟惰性佇列相比普通佇列,效能差異並不大,在極端情況下還會更好,您覺得呢?

 

使用lazy queue會有以下幾種搭配

 

lazy queue 訊息不持久化 , 但是這種模式還是會把訊息放到硬盤裡,RAM的使用率會一直很穩定,但是重啟後一樣會丟失訊息

lazy queue 訊息持久化,這種方式無疑是最佳搭配,訊息放到硬碟並且不會因為伺服器重啟而丟失,面對高併發也是從容不已。 麵包和咖啡更配哦