Githubhtml
本文旨在指出Spring/Spring Boot中集成JMS的一些性能陷阱,在另外一篇文章Spring JMS各組件詳解裏有Spring JMS組件介紹及如何正確使用的內容。java
Spring提供了JmsTemplate
用來簡化JMS的操做,可是JmsTemplate
的性能是有很大問題的,主要體如今如下幾個方面:git
根據Artemis官方文檔的說法,JmsTemplate
是一種anti-pattern,若是在使用JmsTemplate
的狀況下以爲很慢,那麼就不要怪Artemis。github
而且Connection
,Session
,Producer
,Consumer
這些對象自己設計上是能夠複用的。所以JmsTemplate
的作法會大大下降性能,而且將大部分的時間都花在反覆重建這些對象上。spring
Spring提供的Workaroundapache
可讓JmsTemplate
使用SingleConnectionFactory
避免頻繁建立Connection
的問題。或者其子類CachingConnectionFactory
避免頻繁建立Connection
,Session
,Producer
,Consumer
的問題。api
JmsTemplate#sendAndReceive
方法能夠用來模擬RPC調用過程,內部原理是:session
固然整個過程當中還包括前面提到的建立Connection
,Session
,Producer
,Consumer
的動做。多線程
不出所料,Artemis官方文檔也提到了,頻繁建立臨時Queue是一種anti-pattern,會大大影響性能。併發
使用@JmsListener
註解能夠很方便的用來consume消息,但它是同步而非異步,這個和官方文檔所說的偏偏相反(關於sync/async consume消息的資料)。
Spring Boot的JmsAnnotationDrivenConfiguration
默認使用DefaultJmsListenerContainerFactory
生成DefaultMessageListenerContainer
,而它的內部原理是使用TaskExecutor
發起多個線程同時從Queue中拉取消息,這也就是爲何Spring官方文檔裏說若是監聽的是Topic
且concurrency
> 1,那麼可能會收到重複消息的緣由。
DefaultMessageListenerContainer的javadoc中說道:
Actual MessageListener execution happens in asynchronous work units which are created through Spring's TaskExecutor abstraction
這裏就有個矛盾,若是使用async的方式consume消息,那麼只需給consumer設置MessageListener
就好了,何須使用TaskExecutor
呢?
一看代碼果真不出所料:
DefaultMessageListenerContainer#run
callinvokeListener
AbstractPollingMessageListenerContainer#receiveAndExecute
doReceiveAndExecute
receiveMessage
receiveFromConsumer
JmsDestinationAccessor#receiveFromConsumer
這個方法調用了MessageConsumer#consume
也就是說Spring只是使用一個或多個線程在不停的同步的consume消息而已。
雖然可使用concurrency
參數提升併發,可是多線程從Queue/Topic中consume消息的性能比javax.jms.MessageConsumer#setMessageListener
的方法要低上不少。
有興趣的同窗可使用Artemis Example(下載地址)裏的JMS Perf代碼作一下測試,它的測試代碼用的是javax.jms.MessageConsumer#setMessageListener
的方式來consume消息的,在配置正確的狀況下能夠達到接近10w/s。至於使用MessageConsumer.receive
的性能測試則留給同窗本身作了。
仍是以前提到的JmsAnnotationDrivenConfiguration,使用的DefaultJmsListenerContainerFactoryConfigurer默認是把session設置爲transacted的。
根據測算,當一個session是transacted的時候其性能會相差20%,有興趣的同窗可使用Artemis Example(下載地址)裏的JMS Perf代碼作一下測試。
下載以後找到examples/jms/perf
目錄,看這個目錄下的readme.html得到執行方法,在執行以前修改src/main/resources/perf.properties
文件,下面是部分參數的解釋:
可使用如下兩套配置對比transacted的性能差異:
配置一,transacted=false:
num-messages=100000 num-warmup-messages=1000 message-size=1024 durable=false transacted=false batch-size=1 drain-queue=true destination-lookup=perfQueue connection-factory-lookup=/ConnectionFactory throttle-rate=-1 dups-ok-acknowledge=false disable-message-id=true disable-message-timestamp=true
配置二,transacted=true:
num-messages=100000 num-warmup-messages=1000 message-size=1024 durable=false transacted=true batch-size=1 drain-queue=true destination-lookup=perfQueue connection-factory-lookup=/ConnectionFactory throttle-rate=-1 dups-ok-acknowledge=false disable-message-id=true disable-message-timestamp=true
關於加入事務控制是否會有性能問題沒有實際測試過,不過值得注意的這是Spring Boot的默認行爲。
Spring將JMS的集成變得很是簡單,只需提供幾個配置參數就能夠了,可是隻能提供一種默認配置,無論業務場景如何都使用同一種配置是很是有問題的。
spring-boot提供瞭如下幾種方式(文檔)集成JMS:
這幾種模式的缺點都是隻能配置一個ConnectionFactory,這對於簡單應用來講沒問題,對於複雜應用來講就顯得不夠用了,這讓你喪失了針對不一樣場景配置不一樣ConnectionFactory的機會。
並且Artemis的native模式只支持host:port,不支持更豐富參數的url模式。
看過DefaultJmsListenerContainerFactoryConfigurer
和JmsAnnotationDrivenConfiguration
的代碼就明白了。