ActiveMQ支持的虛擬Destinations分爲有兩種,分別是
Ø 虛擬主題(Virtual Topics)
Ø 組合 Destinations(CompositeDestinations)
這兩種虛擬Destinations可以看做對簡單的topic和queue用法的補充,基於它們可以實現一些簡單有用的EIP功能,虛擬主題類似於1對多的分支功能+消費端的cluster+failover,組合Destinations類似於簡單的destinations直接的路由功能。
1. 虛擬主題(Virtual Topics)
ActiveMQ中,topic只有在持久訂閱(durablesubscription)下是持久化的。存在持久訂閱時,每個持久訂閱者,都相當於一個持久化的queue的客戶端,它會收取所有消息。這種情況下存在兩個問題:
1. 同一應用內consumer端負載均衡的問題:同一個應用上的一個持久訂閱不能使用多個consumer來共同承擔消息處理功能。因爲每個都會獲取所有消息。queue模式可以解決這個問題,broker端又不能將消息發送到多個應用端。所以,既要發佈訂閱,又要讓消費者分組,這個功能jms規範本身是沒有的。
2. 同一應用內consumer端failover的問題:由於只能使用單個的持久訂閱者,如果這個訂閱者出錯,則應用就無法處理消息了,系統的健壯性不高。
爲了解決這兩個問題,ActiveMQ中實現了虛擬Topic的功能。使用起來非常簡單。
對於消息發佈者來說,就是一個正常的Topic,名稱以VirtualTopic.開頭。例如VirtualTopic.TEST。
對於消息接收端來說,是個隊列,不同應用裏使用不同的前綴作爲隊列的名稱,即可表明自己的身份即可實現消費端應用分組。例如Consumer.A.VirtualTopic.TEST,說明它是名稱爲A的消費端,同理Consumer.B.VirtualTopic.TEST說明是一個名稱爲B的客戶端。可以在同一個應用裏使用多個consumer消費此queue,則可以實現上面兩個功能。又因爲不同應用使用的queue名稱不同(前綴不同),所以不同的應用中都可以接收到全部的消息。每個客戶端相當於一個持久訂閱者,而且這個客戶端可以使用多個消費者共同來承擔消費任務。
Virtual Topics使用示例如下
- package kk;
- import java.util.concurrent.atomic.AtomicInteger;
- import javax.jms.Message;
- import javax.jms.MessageConsumer;
- import javax.jms.MessageListener;
- import javax.jms.MessageProducer;
- import javax.jms.Queue;
- import javax.jms.Session;
- import javax.jms.TextMessage;
- import org.apache.activemq.ActiveMQConnection;
- import org.apache.activemq.ActiveMQConnectionFactory;
- import org.apache.activemq.command.ActiveMQQueue;
- import org.apache.activemq.command.ActiveMQTopic;
- public class TestVirtualTopic {
- public static void main(String[] args) {
- try {
- ActiveMQConnectionFactory factoryA = new ActiveMQConnectionFactory(
- "tcp://127.0.0.1:61616");
- Queue queue = new ActiveMQQueue(getVirtualTopicConsumerNameA());
- ActiveMQConnection conn = (ActiveMQConnection) factoryA
- .createConnection();
- conn.start();
- Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
- MessageConsumer consumer1 = session.createConsumer( queue );
- MessageConsumer consumer2 = session.createConsumer( queue );
- MessageConsumer consumer3 = session.createConsumer( new ActiveMQQueue(getVirtualTopicConsumerNameB()) );
- final AtomicInteger aint1 = new AtomicInteger(0);
- MessageListener listenerA = new MessageListener() {
- public void onMessage(Message message) {
- try {
- System.out.println(aint1.incrementAndGet()
- + " => receive from "+ getVirtualTopicConsumerNameA() +": " + message);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- };
- consumer1.setMessageListener(listenerA);
- consumer2.setMessageListener(listenerA);
- final AtomicInteger aint2 = new AtomicInteger(0);
- MessageListener listenerB = new MessageListener() {
- public void onMessage(Message message) {
- try {
- System.out.println(aint2.incrementAndGet()
- + " => receive from "+ getVirtualTopicConsumerNameB() +": " + message);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- };
- consumer3.setMessageListener(listenerB);
- MessageProducer producer = session.createProducer(new ActiveMQTopic(getVirtualTopicName()));
- int index = 0;
- while (index++ < 100) {
- TextMessage message = session.createTextMessage(index
- + " message.");
- producer.send(message);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- protected static String getVirtualTopicName() {
- return "VirtualTopic.TEST";
- }
- protected static String getVirtualTopicConsumerNameA() {
- return "Consumer.A.VirtualTopic.TEST";
- }
- protected static String getVirtualTopicConsumerNameB() {
- return "Consumer.B.VirtualTopic.TEST";
- }
- }
使用同樣queue名稱的消費者會平分所有消息。
從queue接收到的消息,message.getJMSDestination().toString()爲topic://VirtualTopic.TEST,即原始的destination。消息的persistent屬性爲true,即每個相當於一個持久訂閱。
Virtual Topic這個功能特性在broker上有個總開關,useVirtualTopics屬性,默認爲true,設置爲false即可關閉此功能。
當此功能開啓,並且使用了持久化的存儲時,broker啓動的時候會從持久化存儲裏拿到所有的destinations的名稱,如果名稱模式與Virtual Topics匹配,則把它們添加到系統的Virtual Topics列表中去。當然,沒有顯式定義的Virtual Topics,也可以直接使用的,系統會自動創建對應的實際topic。
當有consumer訪問此VirtualTopics時,系統會自動創建持久化的queue,並在每次Topic收到消息時,分發到具體的queue。
消費端使用的queue名稱前綴的Consumer是可以修改的。示例如下:
- <beans
- xmlns="http://www.springframework.org/schema/beans"
- xmlns:amq="http://activemq.apache.org/schema/core"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
- http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">
- <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" />
- <broker xmlns="http://activemq.apache.org/schema/core">
- <destinationInterceptors>
- <virtualDestinationInterceptor>
- <virtualDestinations>
- <virtualTopic name=">"prefix="VirtualTopicConsumers.*."selectorAware="false"/>
- </virtualDestinations>
- </virtualDestinationInterceptor>
- </destinationInterceptors>
- </broker>
- </beans>
前綴修改成了VirtualTopicConsumers。
其實也可以使用postfix屬性設置後綴(貌似一般沒有必要)。
selectorAware屬性則表明如果consumer端有selector,則只有匹配selector的消息纔會分派到對應的queue中去。
2. 組合Destinations (Composite Destinations)