公司有一個應用,多個線程從activeMQ中取消息,隨着業務的擴大,該機器占用的網絡帶寬越來越高。
仔細分析發現,mq入隊時並沒有異常高的網絡流量,僅僅在出隊時會產生很高的網絡流量。
最終發現是spring的jmsTemplate與activemq的prefetch機制配合導致的問題。
研究源碼發現jmsTemplate實現機制是:每次調用receive()時都會創建一個新的consumer對象,用完即銷毀。
正常情況下僅僅會浪費重復創建consumer的資源代價,並不至於產生正常情況十倍百倍的網絡流量。
但是activeMQ有一個提高性能的機制prefetch,此時就會有嚴重的問題。
prefetch機制:
每次consumer連接至MQ時,MQ預先存放許多message到消費者(前提是MQ中存在大量消息),預先存放message的數量取決於prefetchSize(默認為1000)。此機制的目的很顯然,是想讓客戶端代碼用一個consumer反復進行receive操作,這樣能夠大量提高出隊性能。
此機制與jmsTemplate配合時就會產生嚴重的問題,每次jmsTemplate.receive(),都會產生1000個消息的網絡流量,但是因為jmsTemplae並不會重用consumer,導致后面999個消息都被廢棄。反復jmsTemplate.receive()時,表面上看不出任何問題,其實網絡帶寬會造成大量的浪費。
解決方案:
1、若堅持使用jmsTemplate,需要設置prefetch值為1,相當於禁用了activeMQ的prefetch機制,此時感覺最健壯,就算多線程,反復調用jmsTemplate.receive()也不會有任何問題。但是會有資源浪費,因為要反復創建consumer並頻繁與服務器進行數據通信,但在性能要求不高的應用中也不算什么問題。
2、不使用jmsTemplate,手工創建一個consumer,並單線程反復使用它來receive(),此時可以充分利用prefetch機制。配合多線程的方式每個線程擁有自己的一個consumer,此時能夠充分發揮MQ在大吞吐量時的速度優勢。
切記避免多線程使用一個consumer造成的消息混亂。大吞吐量的應用推薦使用方案2,能夠充分利用prefetch機制提高系MQ的吞吐性能。
參考文檔: