ActiveMQ高级特性:ActiveMQ之虚拟主题

来源:互联网 发布:尚学堂java架构师视频 编辑:程序博客网 时间:2024/05/01 13:32

http://blog.csdn.net/zhu_tianwei/article/details/46303419


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名称不同(前缀不同),所以不同的应用中都可以接收到全部的消息。每个客户端相当于一个持久订阅者,而且这个客户端可以使用多个消费者共同来承担消费任务。

生产者:

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. package cn.slimsmart.activemq.demo.virtualtopic;  
  2.   
  3. import javax.jms.Connection;  
  4. import javax.jms.DeliveryMode;  
  5. import javax.jms.JMSException;  
  6. import javax.jms.MessageProducer;  
  7. import javax.jms.Session;  
  8. import javax.jms.TextMessage;  
  9. import javax.jms.Topic;  
  10.   
  11. import org.apache.activemq.ActiveMQConnectionFactory;  
  12.   
  13. public class Producer {  
  14.   
  15.     public static void main(String[] args) throws JMSException {  
  16.         // 连接到ActiveMQ服务器  
  17.         ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("tcp://192.168.18.67:61616");  
  18.         Connection connection = factory.createConnection();  
  19.         connection.start();  
  20.         Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);  
  21.         // 创建主题  
  22.         Topic topic = session.createTopic("VirtualTopic.TEST");  
  23.         MessageProducer producer = session.createProducer(topic);  
  24.         // NON_PERSISTENT 非持久化 PERSISTENT 持久化,发送消息时用使用持久模式  
  25.         producer.setDeliveryMode(DeliveryMode.PERSISTENT);  
  26.         TextMessage message = session.createTextMessage();  
  27.         message.setText("topic 消息。");  
  28.         message.setStringProperty("property""消息Property");  
  29.         // 发布主题消息  
  30.         producer.send(message);  
  31.         System.out.println("Sent message: " + message.getText());  
  32.         session.close();  
  33.         connection.close();  
  34.     }  
  35. }  

消费者:

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. package cn.slimsmart.activemq.demo.virtualtopic;  
  2.   
  3. import javax.jms.Connection;  
  4. import javax.jms.JMSException;  
  5. import javax.jms.Message;  
  6. import javax.jms.MessageConsumer;  
  7. import javax.jms.MessageListener;  
  8. import javax.jms.Queue;  
  9. import javax.jms.Session;  
  10. import javax.jms.TextMessage;  
  11.   
  12. import org.apache.activemq.ActiveMQConnectionFactory;  
  13.   
  14. public class Consumer {  
  15.   
  16.     public static void main(String[] args) throws JMSException, InterruptedException {  
  17.         // 连接到ActiveMQ服务器  
  18.         ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("tcp://192.168.18.67:61616");  
  19.         Connection connection = factory.createConnection();  
  20.         connection.start();  
  21.         Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);  
  22.         // 创建主题   
  23.         Queue topicA = session.createQueue("Consumer.A.VirtualTopic.TEST");  
  24.         Queue topicB = session.createQueue("Consumer.B.VirtualTopic.TEST");  
  25.         // 消费者A组创建订阅  
  26.         MessageConsumer consumerA1 = session.createConsumer(topicA);  
  27.         consumerA1.setMessageListener(new MessageListener() {  
  28.             // 订阅接收方法  
  29.             public void onMessage(Message message) {  
  30.                 TextMessage tm = (TextMessage) message;  
  31.                 try {  
  32.                     System.out.println("Received message A1: " + tm.getText()+":"+tm.getStringProperty("property"));  
  33.                 } catch (JMSException e) {  
  34.                     e.printStackTrace();  
  35.                 }  
  36.             }  
  37.         });  
  38.           
  39.         MessageConsumer consumerA2 = session.createConsumer(topicA);  
  40.         consumerA2.setMessageListener(new MessageListener() {  
  41.             // 订阅接收方法  
  42.             public void onMessage(Message message) {  
  43.                 TextMessage tm = (TextMessage) message;  
  44.                 try {  
  45.                     System.out.println("Received message A2: " + tm.getText()+":"+tm.getStringProperty("property"));  
  46.                 } catch (JMSException e) {  
  47.                     e.printStackTrace();  
  48.                 }  
  49.             }  
  50.         });  
  51.           
  52.         //消费者B组创建订阅  
  53.         MessageConsumer consumerB1 = session.createConsumer(topicB);  
  54.         consumerB1.setMessageListener(new MessageListener() {  
  55.             // 订阅接收方法  
  56.             public void onMessage(Message message) {  
  57.                 TextMessage tm = (TextMessage) message;  
  58.                 try {  
  59.                     System.out.println("Received message B1: " + tm.getText()+":"+tm.getStringProperty("property"));  
  60.                 } catch (JMSException e) {  
  61.                     e.printStackTrace();  
  62.                 }  
  63.             }  
  64.         });  
  65.         MessageConsumer consumerB2 = session.createConsumer(topicB);  
  66.         consumerB2.setMessageListener(new MessageListener() {  
  67.             // 订阅接收方法  
  68.             public void onMessage(Message message) {  
  69.                 TextMessage tm = (TextMessage) message;  
  70.                 try {  
  71.                     System.out.println("Received message B2: " + tm.getText()+":"+tm.getStringProperty("property"));  
  72.                 } catch (JMSException e) {  
  73.                     e.printStackTrace();  
  74.                 }  
  75.             }  
  76.         });  
  77.         session.close();  
  78.         connection.close();  
  79.     }  
  80. }  
使用同样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是可以修改的。示例如下:

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. <beans  
  2.   xmlns="http://www.springframework.org/schema/beans"  
  3.   xmlns:amq="http://activemq.apache.org/schema/core"  
  4.   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd  
  6.   http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">  
  7.    
  8. <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" />  
  9.    
  10.   <broker xmlns="http://activemq.apache.org/schema/core">  
  11.     <destinationInterceptors>  
  12.       <virtualDestinationInterceptor>  
  13.         <virtualDestinations>  
  14.           <virtualTopic name=">"prefix="VirtualTopicConsumers.*."selectorAware="false"/>  
  15.         </virtualDestinations>  
  16.       </virtualDestinationInterceptor>  
  17.     </destinationInterceptors>  
  18.    
  19.   </broker>  
  20. </beans>  
前缀修改成了VirtualTopicConsumers。其实也可以使用postfix属性设置后缀(貌似一般没有必要)。selectorAware属性则表明如果consumer端有selector,则只有匹配selector的消息才会分派到对应的queue中去。


这个例子运行的时候,先运行发送的类,再运行收消息的,改了下,加了循环,但是之前循环次数少的时候,10,20,总是A1和B1获取到了消息,A2和B2取不到,并不是对半分的消息,把循环次数加大,A1和B1才取到,但是数量特别少,

       for(int i=0;i<400;i++){            message.setText("topic 消息。" + i);              message.setStringProperty("property", "消息Property");              // 发布主题消息              producer.send(message);              System.out.println("Sent message: " + message.getText());          }   

想了下,是因为A1,B1先注册,注册完事就开始收消息了,改成先运行收消息的,再运行发消息的就正常了,A1,B1,A2,B2平均的收消息了,

 // 消费者A组创建订阅                  MessageConsumer consumerA1 = session.createConsumer(topicA);                        MessageConsumer consumerA2 = session.createConsumer(topicA);                    //消费者B组创建订阅          MessageConsumer consumerB1 = session.createConsumer(topicB);                 MessageConsumer consumerB2 = session.createConsumer(topicB);                        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();                  }              }          });                  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();                  }              }          });                 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();                  }              }          });                  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();                  }              }          });                                         Thread.sleep(1000000);





0 0