ActiveMQ之虛擬主題和鏡像隊列

轉自:https://blog.csdn.net/weixin_40904369/article/details/80403601

ActiveMQ支持的虛擬Destinations分爲有兩種,分別是

1.虛擬主題(Virtual Topics)
2.組合 Destinations(CompositeDestinations)

這兩種虛擬Destinations可以看做對簡單的topic和queue用法的補充,基於它們可以實現一些簡單有用的EIP功能,虛擬主題類似於1對多的分支功能+消費端的cluster+failover,組合Destinations類似於簡單的destinations直接的路由功能。

虛擬主題(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名稱不同(前綴不同),所以不同的應用中都可以接收到全部的消息。每個客戶端相當於一個持久訂閱者,而且這個客戶端可以使用多個消費者共同來承擔消費任務。

生產者:

package cn.slimsmart.activemq.demo.virtualtopic;  
  
import javax.jms.Connection;  
import javax.jms.DeliveryMode;  
import javax.jms.JMSException;  
import javax.jms.MessageProducer;  
import javax.jms.Session;  
import javax.jms.TextMessage;  
import javax.jms.Topic;  
  
import org.apache.activemq.ActiveMQConnectionFactory;  
  
public class Producer {  
  
    public static void main(String[] args) throws JMSException {  
        // 連接到ActiveMQ服務器  
        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("tcp://192.168.18.67:61616");  
        Connection connection = factory.createConnection();  
        connection.start();  
        Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);  
        // 創建主題  
        Topic topic = session.createTopic("VirtualTopic.TEST");  
        MessageProducer producer = session.createProducer(topic);  
        // NON_PERSISTENT 非持久化 PERSISTENT 持久化,發送消息時用使用持久模式  
        producer.setDeliveryMode(DeliveryMode.PERSISTENT);  
        TextMessage message = session.createTextMessage();  
        message.setText("topic 消息。");  
        message.setStringProperty("property", "消息Property");  
        // 發佈主題消息  
        producer.send(message);  
        System.out.println("Sent message: " + message.getText());  
        session.close();  
        connection.close();  
    }  
}  

 

消費者:

package cn.slimsmart.activemq.demo.virtualtopic;  
  
import javax.jms.Connection;  
import javax.jms.JMSException;  
import javax.jms.Message;  
import javax.jms.MessageConsumer;  
import javax.jms.MessageListener;  
import javax.jms.Queue;  
import javax.jms.Session;  
import javax.jms.TextMessage;  
  
import org.apache.activemq.ActiveMQConnectionFactory;  
  
public class Consumer {  
  
    public static void main(String[] args) throws JMSException, InterruptedException {  
        // 連接到ActiveMQ服務器  
        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("tcp://192.168.18.67:61616");  
        Connection connection = factory.createConnection();  
        connection.start();  
        Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);  
        // 創建主題   
        Queue topicA = session.createQueue("Consumer.A.VirtualTopic.TEST");  
        Queue topicB = session.createQueue("Consumer.B.VirtualTopic.TEST");  
        // 消費者A組創建訂閱  
        MessageConsumer consumerA1 = session.createConsumer(topicA);  
        consumerA1.setMessageListener(new MessageListener() {  
            // 訂閱接收方法  
            public void onMessage(Message message) {  
                TextMessage tm = (TextMessage) message;  
                try {  
                    System.out.println("Received message A1: " + tm.getText()+":"+tm.getStringProperty("property"));  
                } catch (JMSException e) {  
                    e.printStackTrace();  
                }  
            }  
        });  
          
        MessageConsumer consumerA2 = session.createConsumer(topicA);  
        consumerA2.setMessageListener(new MessageListener() {  
            // 訂閱接收方法  
            public void onMessage(Message message) {  
                TextMessage tm = (TextMessage) message;  
                try {  
                    System.out.println("Received message A2: " + tm.getText()+":"+tm.getStringProperty("property"));  
                } catch (JMSException e) {  
                    e.printStackTrace();  
                }  
            }  
        });  
          
        //消費者B組創建訂閱  
        MessageConsumer consumerB1 = session.createConsumer(topicB);  
        consumerB1.setMessageListener(new MessageListener() {  
            // 訂閱接收方法  
            public void onMessage(Message message) {  
                TextMessage tm = (TextMessage) message;  
                try {  
                    System.out.println("Received message B1: " + tm.getText()+":"+tm.getStringProperty("property"));  
                } catch (JMSException e) {  
                    e.printStackTrace();  
                }  
            }  
        });  
        MessageConsumer consumerB2 = session.createConsumer(topicB);  
        consumerB2.setMessageListener(new MessageListener() {  
            // 訂閱接收方法  
            public void onMessage(Message message) {  
                TextMessage tm = (TextMessage) message;  
                try {  
                    System.out.println("Received message B2: " + tm.getText()+":"+tm.getStringProperty("property"));  
                } catch (JMSException e) {  
                    e.printStackTrace();  
                }  
            }  
        });  
        session.close();  
        connection.close();  
    }  
}  

使用同樣queue名稱的消費者會平分所有消息。
從queue接收到的消息,message.getJMSDestination().toString()爲topic://VirtualTopic.TEST,即原始的destination。消息的persistent屬性爲true,即每個相當於一個持久訂閱。
A1和A2爲一個應用,B1和B2爲一個應用,2組應用內部做負載,和failover。

 

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中去。

Mirrored Queue 鏡像隊列

ActiveMQ每一個queue中消息只能被一個消費者消費,然而,有時候,你希望能夠監視生產者和消費者之間的消息流。你可以通過使用VirtualDestinations來建立一個virtualqueue來吧消息轉發到多個queue中。但是,爲系統每一個queue都進行如此的配置可能會很麻煩。

MirroredQueue: Broker會把發送到某一個隊列上的所有消息轉發到一個名稱類似的topic,因此監控程序只需要訂閱這個mirroredqueue topic.爲啓用MirroredQueue,首先要將BrokerService的useMirrored

Queues屬性設置爲true:

然後可以通過destinationInterceptors設置其屬性,如mirrortopic的前綴,缺省是VritualTopic.Mirror.


<broker xmlns="http://activemq.apache.org/schema/core" useMirroredQueue="true">

</broker>

修改後綴的配置示例:

<broker xmlns="http://activemq.apache.org/schema/core">
       <destinationInterceptors>
              <mirroredQueue copyMessage="true" postfix=".qmirror" prefix="" />
       </destinationInterceptors>
</broker>

根虛擬topic相反,這時發佈的是隊列,但是消費者時topic,相當於消費者主題化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章