我們的伺服器使用RabbitMQ作為訊息中轉的容器。某天我懷疑RabbitMQ隊列 是否都能及時消化。於是用命令查詢了下:rabbitmqctl list_vhosts | grep -P ".*\.host" | xargs -i rabbitmqctl list_queues -p {} | grep "queue"。 不查不知道,一查嚇一跳:大多數伺服器的隊列基本上都是空的,但是有些伺服器的某個隊列竟然有超過500W條的記錄。一般 RabbitMQ進程佔用記憶體不過100M-200M,這些隊列超長的 RabbitMQ 進程可以佔用超過2G的記憶體。
顯然訊息佇列的消費者出現了問題。開發查看日誌發現作為該隊列消費者的Java服務的日誌也卡住了,重啟服務 後 (這點做得不對,應該用jstat、jstack進行排查,而不是直接重啟)又很快卡住。這時候他才想起來用jstat,通過jstat發現JVM記憶體都耗盡了,之後進入無盡的Full GC,所以當然不會處理隊列訊息和輸出日誌資訊了。jstat的輸出如下: ------------------------------------- ------------------------------------- [root@mail ~]# jstat -gcutil 29389 S0 S1 E O P YGC YGCT FGC FGCT GCT 100.00 0.00 100.00 100.00 59.59 1639 2.963 219078 99272.246 99275.209 ------------------------------------- ------------------------------------- 使用jmap匯出這時候的Java堆棧,命令:jmap -dump:format=b,file= 29389.hprof 29389。將得到的dump檔案放到MAT( Eclipse Memory Analyzer) 裡進行分析,發現很明顯是QueueingConsumer持有大量對象導致JVM記憶體溢出,截圖如下: 上網搜尋了下,發現有人遇到了類似的問題: RabbitMQ QueueingConsumer possible memory leak 。解決辦法是調用Channel的basicQos方法,設定臨時記憶體中最多儲存的訊息數。這個數值的設定建議參考 《Some queuing theory: throughput, latency and bandwidth》 權衡決定。 拍腦袋將 basicQos設定為16後重啟服務,隊列終於開始消化了。用jstat 觀察JVM記憶體使用量情況,也沒有再出現劇增溢出的現象。 總結:使用RabbitMQ的時候,一定要合理地設定QoS參數。 我感覺RabbitMQ的預設做法其實是很脆弱的,容易導致 雪崩。“You have a queue in Rabbit. You have some clients consuming from that queue. If you don't set a QoS setting at all (basic.qos), then Rabbit will push all the queue's messages to the clients as fast as the network and the clients will allow.“。 這樣如果由於某些原因,隊列中堆積了比較多的訊息,就可能導致Comsumer記憶體溢出卡死,於是發生 惡性迴圈,隊列訊息不斷堆積得不到消化, 徹底地悲劇了。