消息持久化是
RabbitMQ
最爲人津津樂道的特性之一,
RabbitMQ
可以在付出最小的性能代價的基礎上實現消息的持久化,最大的奧祕就在於
RabbitMQ
多層消息隊列的設計上。下面,本文就從
MessageQueue
的設計和消息在
MessageQueue
的生命週期兩個方面全面介紹
RabbitMQ
的消息隊列。
RabbitMQ徹底實現了AMQP協議,相似於一個郵箱服務。Exchange負責根據ExchangeType和RoutingKey將消息投遞到對應的消息隊列中,消息隊列負責在消費者獲取消息前暫存消息。在RabbitMQ中,MessageQueue主要由兩部分組成,一個爲AMQQueue,主要負責實現AMQP協議的邏輯功能。另一個是用來存儲消息的BackingQueue,本文重點關注的是BackingQueue的設計。 性能
在RabbitMQ中BackingQueue又由5個子隊列組成:Q1、Q2、Delta、Q3和Q4。RabbitMQ中的消息一旦進入隊列,不是固定不變的,它會隨着系統的負載在隊列中不斷流動,消息的狀態不斷髮生變化。RabbitMQ中的消息一共有5種狀態: spa
a)Alpha:消息的內容和消息索引都保存在內存中; 設計
b)Beta:消息內容保存在磁盤上,消息索引保存在內存中; 索引
c)Gamma:消息內容保存在磁盤上,消息索引在磁盤和內存都有; 生命週期
d)Delta:消息內容和索引都在磁盤上; 隊列
注意:對於持久化的消息,消息內容和消息索引都必須先保存到磁盤上,纔會處於上述狀態中的一種,而Gamma狀態的消息只有持久化的消息纔會有該狀態。 內存
BackingQueue
中的
5
個子隊列中的消息狀態,
Q1
和
Q4
對應的是
Alpha
狀態,
Q2
和
Q3
是
Beta
狀態,
Delta
對應的是
Delta
狀態。上述就是
RabbitMQ
的多層隊列結構的設計,咱們能夠看出從
Q1
到
Q4
,基本經歷的是由
RAM
到
DISK
,再到
RAM
的設計。這樣的設計的好處就是當隊列負載很高的狀況下,可以經過將一部分消息由磁盤保存來節省內存空間,當負載下降的時候,這部分消息又漸漸回到內存,被消費者獲取,使得整個隊列有很好的彈性。下面咱們就來看一下,整個消息隊列的工做流程。
引發消息流動主要有兩方面的因素:其一是消費者獲取消息;其二是因爲內存不足,引發消息的換出到磁盤上(
Q1-.>Q2
、
Q2->Delta
、
Q3->Delta
、
Q4->Q3
)。
RabbitMQ
在系統運行時會根據消息傳輸的速度計算一個當前內存中可以保存的最大消息數量(
Target_RAM_Count
),當內存中的消息數量大於該值時,就會引發消息的流動。進入隊列的消息,通常會按着
Q1->Q2->Delta->Q3->Q4
的順序進行流動,可是並非每條消息都必定會經歷全部的狀態,這個取決於當時系統的負載情況。
當消費者獲取消息時,首先會從
Q4
隊列中獲取消息,若是
Q4
獲取成功,則返回,若是
Q4
爲空,則嘗試從
Q3
獲取消息;首先,系統會判斷
Q3
隊列是否爲空,若是爲空,則直接返回隊列爲空,即此時隊列中無消息(後續會論證)。若是不爲空,則取出
Q3
的消息,而後判斷此時
Q3
和
Delta
隊列的長度,若是都爲空,則可認爲
Q2
、
Delta
、
Q3
和
Q4
所有爲空
(
後續說明
)
,此時將
Q1
中消息直接轉移到
Q4
中,下次直接從
Q4
中獲取消息。若是
Q3
爲空,
Delta
不空,則將
Delta
中的消息轉移到
Q3
中;若是
Q3
非空,則直接下次從
Q3
中獲取消息。在將
Delta
轉移到
Q3
的過程當中,
RabbitMQ
是按照索引分段讀取的,首先讀取某一段,直到讀到的消息非空爲止,而後判斷讀取的消息個數與
Delta
中的消息個數是否相等,若是相等,則判定此時
Delta
中已無消息,則直接將
Q2
和剛讀到的消息一併放入
Q3
中。若是不相等,則僅將這次讀到的消息轉移到
Q3
中。這就是消費者引發的消息流動過程。
下面咱們分析一下因爲內存不足引發的消息換出。消息換出的條件是內存中保存的消息數量
+
等待
ACK
的消息的數量
>Target_RAM_Count
。當條件觸發時,系統首先會判斷若是當前進入等待
ACK
的消息的速度大於進入隊列的消息的速度時,會先處理等待
ACK
的消息。步驟基本上
Q1->Q2
或者
Q3
移動,取決於
Delta
隊列是否爲空。
Q4->Q3
移動,
Q2
和
Q3
向
Delta
移動。
最後,咱們來分析一下前面遺留的兩個問題,一個是爲何
Q3
隊列爲空便可認定整個隊列爲空。試想若是
Q3
爲空,
Delta
不空,則在
Q3
取出最後一條消息時,
Delta
上的消息就會被轉移到
Q3
上,與
Q3
空矛盾。若是
Q2
不空,則在
Q3
取出最後一條消息,若是
Delta
爲空時,會將
Q2
的消息併入
Q3
,與
Q3
爲空矛盾。若是
Q1
不空,則在
Q3
取出最後一條消息,若是
Delta
和
Q3
均爲空時,則將
Q1
的消息轉移到
Q4
中,與
Q4
爲空矛盾。這也解釋了另一個問題,即爲何
Q3
和
Delta
爲空,
Q2
就爲空。
上述就是整個消息在
RabbitMQ
隊列中流動過程。從上述流程能夠看出,消息若是可以被儘早消費掉,就不須要經歷持久化的過程,由於這樣會加系統的開銷。若是消息被消費的速度過慢,
RabbitMQ
經過換出內存的方式,防止內存溢出。