2.6 Features
ActiveMQ包含了很多功能強大的特性,下面簡要介紹其中的幾個。
2.6.1 Exclusive Consumer
Queue中的訊息是按照順序被分發到consumers的。然而,當你有多個consumers同時從相同的queue中提取訊息時,你將失去這個保證。因為這些訊息是被多個線程並發的處理。有的時候,保證訊息按照順序處理是很重要的。例如,你可能不希望在插入訂單操作結束之前執行更新這個訂單的操作。
ActiveMQ從4.x版本起開始支援Exclusive Consumer (或者說Exclusive Queues)。 Broker會從多個consumers中挑選一個consumer來處理queue中所有的訊息,從而保證了訊息的有序處理。如果這個consumer失效,那麼broker會自動切換到其它的consumer。
可以通過Destination Options 來建立一個Exclusive Consumer,如下: Java代碼 queue = new ActiveMQQueue("TEST.QUEUE?consumer.exclusive=true"); consumer = session.createConsumer(queue);
queue = new ActiveMQQueue("TEST.QUEUE?consumer.exclusive=true");consumer = session.createConsumer(queue); 順便說一下,可以給consumer設定優先權,以便針對網路情況(如network hops)進行最佳化,如下: Java代碼 queue =
new ActiveMQQueue("TEST.QUEUE?consumer.exclusive=true &consumer.priority=10");
queue = new ActiveMQQueue("TEST.QUEUE?consumer.exclusive=true &consumer.priority=10");
2.6.2 Message Groups
用Apache官方文檔的話說,Message Groups rock。它是Exclusive Consumer功能的增強。邏輯上,Message Groups 可以看成是一種並發的Exclusive Consumer。跟所有的訊息都由唯一的consumer處理不同,JMS 訊息屬性JMSXGroupID 被用來區分message group。Message Groups特性保證所有具有相同JMSXGroupID 的訊息會被分發到相同的consumer(只要這個consumer保持active)。另外一方面,Message Groups特性也是一種負載平衡的機制。
在一個訊息被分發到consumer之前,broker首先檢查訊息JMSXGroupID屬性。如果存在,那麼broker 會檢查是否有某個consumer擁有這個message group。如果沒有,那麼broker會選擇一個consumer,並將它關聯到這個message group。此後,這個consumer會接收這個message group的所有訊息,直到: Consumer被關閉。 Message group被關閉。通過發送一個訊息,並設定這個訊息的JMSXGroupSeq為0。
從4.1版本開始,ActiveMQ支援一個布爾欄位JMSXGroupFirstForConsumer 。當某個message group的第一個訊息被發送到consumer的時候,這個欄位被設定。如果客戶使用failover transport串連到broker。在由於網路問題等造成客戶重新串連到broker的時候,相同message group的訊息可能會被分發到不同與之前的consumer,因此JMSXGroupFirstForConsumer欄位也會被重新設定。
以下是使用message groups的例子: Java代碼 Mesasge message = session.createTextMessage("<foo>hey</foo>"); message.setStringProperty("JMSXGroupID", "IBM_NASDAQ_20/4/05"); ... producer.send(message);
Mesasge message = session.createTextMessage("<foo>hey</foo>");message.setStringProperty("JMSXGroupID", "IBM_NASDAQ_20/4/05");...producer.send(message);
2.6.3 JMS Selectors
JMS Selectors用於在訂閱中,基於訊息屬性對進行訊息的過濾。JMS Selectors由SQL92文法定義。以下是個Selectors的例子:
Java代碼 consumer = session.createConsumer(destination, "JMSType = 'car' AND weight > 2500");
consumer = session.createConsumer(destination, "JMSType = 'car' AND weight > 2500");
在JMS Selectors運算式中,可以使用IN、NOT IN、LIKE等,例如:
LIKE '12%3' ('123' true,'12993' true,'1234' false)
LIKE 'l_se' ('lose' true,'loose' false)
LIKE '/_%' ESCAPE '/' ('_foo' true,'foo' false)
需要注意的是,JMS Selectors運算式中的日期和時間需要使用標準的long型毫秒值。另外運算式中的屬性不會自動進行類型轉換,例如:
Java代碼 myMessage.setStringProperty("NumberOfOrders", "2");
myMessage.setStringProperty("NumberOfOrders", "2"); "NumberOfOrders > 1" 求值結果是false。關於JMS Selectors的詳細文檔請參考javax.jms.Message的javadoc。
上一小節介紹的Message Groups雖然可以保證具有相同message group的訊息被唯一的consumer順序處理,但是卻不能確定被哪個consumer處理。在某些情況下,Message Groups可以和JMS Selector一起工作,例如:
設想有三個consumers分別是A、B和C。你可以在producer中為訊息設定三個message groups分別是"A"、"B"和"C"。然後令consumer A使用"JMXGroupID = 'A'"作為selector。B和C也同理。這樣就可以保證message group A的訊息只被consumer A處理。需要注意的是,這種做法有以下缺點:
producer必須知道當前正在啟動並執行consumers,也就是說producer和consumer被耦合到一起。 如果某個consumer失效,那麼應該被這個consumer消費的訊息將會一直被積壓在broker上。
2.6.4 Pending Message Limit Strategy
首先簡要介紹一下prefetch機制。ActiveMQ通過prefetch機制來提高效能,這意味這用戶端的記憶體裡可能會緩衝一定數量的訊息。緩衝訊息的數量由prefetch limit來控制。當某個consumer的prefetch buffer已經達到上限,那麼broker不會再向consumer分發訊息,直到consumer向broker發送訊息的確認。可以通過在ActiveMQConnectionFactory或者ActiveMQConnection上設定ActiveMQPrefetchPolicy對象來配置prefetch policy。也可以通過connection options或者destination options來配置。例如:
tcp://localhost:61616?jms.prefetchPolicy.all=50
tcp://localhost:61616?jms.prefetchPolicy.queuePrefetch=1
queue = new ActiveMQQueue("TEST.QUEUE?consumer.prefetchSize=10");
prefetch size的預設值如下: persistent queues (default value: 1000) non-persistent queues (default value: 1000) persistent topics (default value: 100) non-persistent topics (default value: Short.MAX_VALUE -1)
慢消費者會在非持久的topics上導致問題:一旦訊息積壓起來,會導致broker把大量訊息儲存在記憶體中,broker也會因此而變慢。未來ActiveMQ可能會實現磁碟緩衝,但是這也還是會存在效能問題。目前ActiveMQ使用Pending Message Limit Strategy來解決這個問題。除了prefetch buffer之外,你還要配置緩衝訊息的上限,超過這個上限後,新訊息到來時會丟棄舊訊息。通過在設定檔的destination map中配置PendingMessageLimitStrategy,可以為不用的topic namespace配置不同的策略。目前有以下兩種: ConstantPendingMessageLimitStrategy。這個策略使用常量限制。
例如:<constantPendingMessageLimitStrategy limit="50"/> PrefetchRatePendingMessageLimitStrategy。這個策略使用prefetch size的倍數限制。
例如:<prefetchRatePendingMessageLimitStrategy multiplier="2.5"/>
在以上兩種方式中,如果設定0意味著除了prefetch之外不再緩衝訊息;如果設定-1意味著禁止丟棄訊息。
此外,你還可以配置訊息的丟棄策略,目前有以下兩種: oldestMessageEvictionStrategy。這個策略丟棄最舊的訊息。 oldestMessageWithLowestPriorityEvictionStrategy。這個策略丟棄最舊的,而且具有最低優先順序的訊息。
以下是個ActiveMQ設定檔的例子: Xml代碼 <broker persistent="false" brokerName="${brokername}" xmlns="http://activemq.org/config/1.0"> <destinationPolicy> <policyMap> <policyEntries> <policyEntry topic="PRICES.>"> <!-- 10 seconds worth --> <subscriptionRecoveryPolicy> <timedSubscriptionRecoveryPolicy recoverDuration="10000" /> </subscriptionRecoveryPolicy> <!-- lets force old messages to be discarded for slow consumers --> <pendingMessageLimitStrategy> <constantPendingMessageLimitStrategy limit="10"/> </pendingMessageLimitStrategy> </policyEntry> </policyEntries> </policyMap> </destinationPolicy> ... </broker>
<broker persistent="false" brokerName="${brokername}" xmlns="http://activemq.org/config/1.0"> <destinationPolicy> <policyMap> <policyEntries> <policyEntry topic="PRICES.>"> <!-- 10 seconds worth --> <subscriptionRecoveryPolicy> <timedSubscriptionRecoveryPolicy recoverDuration="10000" /> </subscriptionRecoveryPolicy> <!-- lets force old messages to be discarded for slow consumers --> <pendingMessageLimitStrategy> <constantPendingMessageLimitStrategy limit="10"/> </pendingMessageLimitStrategy> </policyEntry> </policyEntries> </policyMap> </destinationPolicy> ...</broker>
2.6.5 Composite Destinations
從1.1版本起, ActiveMQ支援composite destinations。它允許用一個虛擬destination 代表多個destinations。例如你可以通過composite destinations在一個操作中同時向12個queue發送訊息。在composite destinations中,多個destination之間採用","分割。例如: Java代碼 Queue queue = new ActiveMQQueue("FOO.A,FOO.B,FOO.C");
Queue queue = new ActiveMQQueue("FOO.A,FOO.B,FOO.C");
如果你希望使用不同類型的destination,那麼需要加上首碼如queue:// 或topic://,例如: Java代碼 Queue queue = new ActiveMQQueue("FOO.A,topic://NOTIFY.FOO.A");
Queue queue = new ActiveMQQueue("FOO.A,topic://NOTIFY.FOO.A");
以下是ActiveMQ設定檔進行配置的一個例子: Xml代碼 <destinationInterceptors> <virtualDestinationInterceptor> <virtualDestinations> <compositeQueue name="MY.QUEUE"> <forwardTo> <queue physicalName="FOO" /> <topic physicalName="BAR" /> </forwardTo> </compositeQueue> </virtualDestinations> </virtualDestinationInterceptor> </destinationInterceptors>
<destinationInterceptors> <virtualDestinationInterceptor> <virtualDestinations> <compositeQueue name="MY.QUEUE"> <forwardTo> <queue physicalName="FOO" /> <topic physicalName="BAR" /> </forwardTo> </compositeQueue> </virtualDestinations> </virtualDestinationInterceptor></destinationInterceptors>
可以在轉寄前,先通過JMS Selector判斷一個訊息是否需要轉寄,例如: Xml代碼 <destinationInterceptors> <virtualDestinationInterceptor> <virtualDestinations> <compositeQueue name="MY.QUEUE"> <forwardTo> <filteredDestination selector="odd = 'yes'" queue="FOO"/> <filteredDestination selector="i = 5" topic="BAR"/> </forwardTo> </compositeQueue> </virtualDestinations> </virtualDestinationInterceptor> </destinationInterceptors>
<destinationInterceptors> <virtualDestinationInterceptor> <virtualDestinations> <compositeQueue name="MY.QUEUE"> <forwardTo> <filteredDestination selector="odd = 'yes'" queue="FOO"/> <filteredDestination selector="i = 5" topic="BAR"/> </forwardTo> </compositeQueue> </virtualDestinations> </virtualDestinationInterceptor></destinationInterceptors>
2.6.6 Mirrored Queues
每個queue中的訊息只能被一個consumer消費。然而,有時候你可能希望能夠監視生產者和消費者之間的訊息流程。你可以通過使用Virtual Destinations 來建立一個virtual queue 來把訊息轉寄到多個queues中。但是 為系統中每個queue都進行如此的配置可能會很麻煩。
ActiveMQ支援Mirrored Queues。Broker會把發送到某個queue的所有訊息轉寄到一個名稱類似的topic,因此監控程式可以訂閱這個mirrored queue topic。為了啟用Mirrored Queues,首先要將BrokerService的useMirroredQueues屬性設定成true,然後可以通過destinationInterceptors設定其它屬性,如mirror topic的首碼,預設是"VirtualTopic.Mirror."。以下是ActiveMQ設定檔的一個例子: Xml代碼 <broker xmlns="http://activemq.org/config/1.0" brokerName="MirroredQueuesBroker1" useMirroredQueues="true"> <transportConnectors> <transportConnector uri="tcp://localhost:61616"/> </transportConnectors> <destinationInterceptors> <mirroredQueue copyMessage = "true" prefix="Mirror.Topic"/> </destinationInterceptors> ... </broker>