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 消息持久化,這種方式無疑是最佳搭配,消息放到硬盤並且不會因為服務器重啟而丟失,面對高並發也是從容不已。 面包和咖啡更配哦