文章主要是兩部分,第一部分介紹 RabbitMQ 的基礎信息,網上有很多,如果對 RabbitMQ 比較熟悉可以略過,第二部分主要講述遇到的問題和影響性能的一些因素以及解決辦法。
第一部分
RabbitMQ 基礎介紹
RabbitMQ 是基于 Erlang 語言實現 AMQP(高級消息隊列協議)的消息中間件的一種,最初起源于金融系統,用于在分布式系統中存儲轉發消息,在易用性、擴展性、高可用性等方面表現不俗。
RabbitMQ 主要是為了實現系統之間的雙向解耦而實現的,消息的發送者無需知道消息使用者的存在,反之亦然。
一些 RabbitMQ 術語
- Channel
- Producer
- Exchange
- Queue
- Consumer
數據流
Channel
客戶端和服務建立連接,通過 Channel 向 Exchange 發送消息,Exchange 通過 Routing Key 將消息路由到 Queue (Exchange 和 Queue 通過 Routing Key 建立綁定關系,Routing Key 也可以為空)。
Exchange (交換機)
接收消息且轉發消息到綁定隊列,Producer 只能將消息發給 Exchange,由 Exchange 將消息轉發到綁定的 Queue,Exchange 常用的幾種類型工作模式,fanout、direct、topic。
fanout 模式即為廣播模式,Exchange 會把同一份消息發送給所有的綁定隊列,每一個隊列收到的消息是相同的。
生產者 P 通過 Exchange X 將消息發給兩個隊列,C1、C2 分別消費對應的隊列,他們得到的數據是相同的。
direct 模式通過 RoutingKey 將消息發送給指定的隊列
生產者 P 通過 Exchange X 將 error 消息發送到 amqp.gen-S9b 隊列,將 info、error、warning 消息發送到 amqp.gen-A1 隊列,他們分別對應著消費者 C1、C2。
topic 模式按照規則轉發,跟 direct 差不多,只是但是支持模式匹配,通配符等,具有更好的靈活性。
topic 模式下,會對 Routing key 做模式匹配,*
代表匹配一個單詞,#
匹配 0 個或多個,*.orange.*
會把指定 Routing key 中間是 orange 的全部路由到 Q1 隊列,而 lazy.#
會把是 lazy.
開頭的消息分發到 Q2 隊列,*.*.rabbit
則代表把前兩個是任意詞,但是結尾是 .rabbit
的消息分發到 Q2 隊列。
交換機的幾個屬性
- Durabilty 是否持久化
- Auto Delete 是否自動刪除
Queue(隊列)
隊列是通過 RoutingKey 和 Exchange 綁定在一起的,Queue 的消息都來自 Exchange,Producer 是無法直接像隊列發送消息的,一個隊列可以和多個 Exchange 綁定在一起。
Queue 的幾個屬性
- Durabilty
- Auto Delete
第二部分
問題:通過 MQ 監控管理后臺發現隊列積壓嚴重,內存消耗 6G 多。
使用場景:移動端上報日志,存儲到 RabbitMQ,之后服務端消耗 MQ 對日志進行格式化后再次分發。
注意上圖中的兩個紅色框處(當時沒有截圖),Consumer utilisation 當時在 7% 左右,Process memory 大約 6G 多,生產者每秒大約產生 2000 條消息,Consumer utilisation 代表了消費者的工作效率,一般效率低有幾種情況
- 消費者太少
- ACK 回執速度太慢
- 消費者太多
首先我們沒有采用 ACK 機制,這樣就不存在這個問題,第二 Exchange 和 Queue 在建立的時候,采用了 fanout 模式并且持久化,持久化和非持久化大約有 10 倍的性能差異,如果只是單存的針對同一個隊列增加 Consumer,并不會改善效率,而 fanout 的廣播模式不利于增加多個 Queue。
解決辦法:重建了 Exchange 和 Queue,采用 direct 模式且非持久化的方式,對同一個 Exchange 綁定了 5 個 Queue,生產者隨機的將消息分發到某個隊列,每個 Queue 會對應一個消費者。因為消息本身是日志,日志有時間的,所以不存在時序的問題,這樣就大大的提高了消費者的工作效率,通過這樣的改進以后,Consomer utilisation 基本處在 100%,而且也沒有出現隊列積壓。
內存與流量控制參數
- prefetch 是每次從一次性從 broker 里面取的待消費的消息的個數,值太大會增加延遲,太小會導致消息積壓。
- vm_memory_high_watermark 內存流量控制,默認 0.4(還可以是絕對值),當占用物理內存的 40% 時,它會引起一個內存報警并且阻塞所有連接。 百分比情況下可使用內存
vm_memory_limit = vm_memory_high_watermark * 物理內存
,絕對值情況下vm_memory_limit = vm_memory_high_watermark
。
$ rabbitmqctl status | grep vm_memory
{vm_memory_high_watermark,0.4},
{vm_memory_limit,40530786713},
- vm_memory_high_watermark_paging_ratio 確定了何時執行消息從內存轉移到硬盤,默認 0.5,當內存消耗 vm_memory_limit * 0.5 時,開始從內存轉移到硬盤。
PS:
使用 RabbitMQ 時,創建 Exchange 和 Queue 要注意自己的使用場景,是否需要持久化,是否需要 ACK 機制,消息分發模式,是否有時序要求等等,千萬不要隨便建個 fanout 模式放那就用。
參考
https://www.rabbitmq.com/documentation.html
https://emacsist.github.io/tags/#rabbitmq
http://lynnkong.iteye.com/blog/1699684
https://www.gitbook.com/book/geewu/rabbitmq-quick