1. Visual Destinations
1.1 概述
虚拟Destination用来创建逻辑Destinations,客户端可以通过它来产生和消费消息,它会把消息映射到物理Destinations。ActiveMQ支持两种方式:
1. 虚拟主题(Virtual Topics)
2. 组合Destinations (Composite Destinations)
1.2 为何使用虚拟主题
ActiveMQ中,topic只有在持久订阅下才会持久化,持久订阅时,每个订阅者,都相当于一个queue的客户端,它会收取所有消息,这种情况下存在两个问题:
1. 同一个应用内的consumer端负载均衡的问题,也就是同一个应用上的一个持久订阅者不能使用多个consumer来共同承担消息处理功能,因为每个consumer都会获取所有消息。
2. 同一应用内的consumer端的failover问题:由于只能使用单个的持久订阅者,如果这个订阅者出错,则应用就无法处理消息了,系统的健壮性不高。
为了解决这两个问题,ActiveMQ中实现了虚拟的Topic的功能。
1.3 如何使用虚拟主题
1. 对于消息发布者来说,就是一个正常的Topic,名称以VirtualTopic开头。例如
VirtualTopic.Orders.代码示例如下:
Topic destination = session.createTopic("VirtualTopic.Orders");
2. 对于消息接收端来说,是一个队列,不同应用里面应使用不同的前缀作为队列的名称,即可表明自己的身份,即可实现消费端应用的分组。
例如Consumer.A.VritualTopic.Orders,说明它是名称A的消费端,同理Consumer.B.VritualTopic.Orders表示时一个名称为B的客户端,可以在同一个应用里面使用多个consumer消费此queue,则可以实现上面两个功能。
又因为不同应用使用的queue名称不同(前缀不同),所以不同的应用中都可以接收到全部的消息,每个客户端相当于一个持久订阅者,而且这个客户端可以使用多个消费者来共同承担消费任务,代码示例如下:
Destination destiantion = session.createQueue("Consumer.A.VirtualTopic.Orders“);
完整得测试代码如下:
消息发送者
package com.wangx.activemq.topic; import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; public class TopicSender { public static void main(String[] args) throws JMSException { ConnectionFactory factory = new ActiveMQConnectionFactory("tcp://localhost:61616"); Connection connection = factory.createConnection(); Session session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE); //创建虚拟主题,加前缀VirtualTopic Topic topic = session.createTopic("VirtualTopic.myTopic"); MessageProducer producer = session.createProducer(topic); producer.setDeliveryMode(DeliveryMode.PERSISTENT); connection.start(); for (int i = 0; i < 30; i++) { TextMessage textMessage = session.createTextMessage("topic消息===" + i); producer.send(textMessage); } session.commit(); connection.close(); } }
A客户端代码(只有一个消费者)
package com.wangx.activemq.topic; import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; public class QR1 { public static void main(String[] args) throws JMSException { ConnectionFactory factory = new ActiveMQConnectionFactory("tcp://localhost:61616"); Connection connection = factory.createConnection(); connection.start(); final Session session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE); //创建队列 Destination destination = session.createQueue("Consumer.A.VirtualTopic.myTopic"); MessageConsumer consumer = session.createConsumer(destination); consumer.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { TextMessage textMessage = (TextMessage) message; try { System.out.println("Consumer.A.接收到得消息:" + textMessage.getText()); session.commit(); } catch (JMSException e) { e.printStackTrace(); } } }); } }
B客户端代码,有两个消费者
package com.wangx.activemq.topic; import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; public class QR2 { public static void main(String[] args) throws JMSException { ConnectionFactory factory = new ActiveMQConnectionFactory("tcp://localhost:61616"); Connection connection = factory.createConnection(); connection.start(); final Session session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE); //创建队列 Destination destination = session.createQueue("Consumer.B.VirtualTopic.myTopic"); final MessageConsumer consumer = session.createConsumer(destination); final MessageConsumer messageConsumer = session.createConsumer(destination); //模拟多个consumer消费一个queue new Thread(new Runnable() { @Override public void run() { try { consumer.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { TextMessage textMessage = (TextMessage) message; try { System.out.println("Consumer.B-->consumer接收到消息:" + textMessage.getText()); session.commit(); } catch (JMSException e) { e.printStackTrace(); } } }); } catch (JMSException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { messageConsumer.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { TextMessage textMessage = (TextMessage) message; try { System.out.println("Consumer.B-->messageConsumer接收到消息:" + textMessage.getText()); session.commit(); } catch (JMSException e) { e.printStackTrace(); } } }); } catch (JMSException e) { e.printStackTrace(); } } }).start(); } }
在接收消息之前,应该先运行一下consumer客户端,将消费者注册到Broker中。
3. 默认虚拟主题得前缀是:VirtualTopic.>
自定义消费虚拟地址默认格式:Consumer.*.VirtualTopic.>
自定义消费虚拟地址可以改,比如下面的配置就把它修改了。
xml配置如下
<broker xmlns="http:/activemq.apache.org/schema/core"> <destinationInterceptors> <virtualDestinationInterceptor> <virtualDestinations> <virtualTopic name=">" prefix="VirtualTopicConsumers.*." selectorAware="false"/> </virtualDestinations> </virtualDestinationInterceptor> </destinationInterceptors> </broker>
配置之后将consumer端的相应前缀修改即可。
2. Mirrored Queues
2.1 概述
ActiveMQ中每个queue中的消息只能被一个consumer消费,然而,有时候你可能希望能够监视生产者和消费者之间的消息流。你可以通过Virtual Destinations来建立一个virtual queue 来把消息发送到多个queues中。但是,为系统中没给queue都进行如此配置可能会很麻烦。
2.1使用
ActiveMQ支持Mirrored Queues。Broker会把发送到某个queue的所有消息都转发到一个名称类似的topic,因此监控程序只需要订阅这个mirrored queue topic。为了启用Mirrored Queues,首先将Broker Service的useMirroredQueues属性设置成true,然后可以通过destinationInterceptors设置其他属性,如:mirror topic的前缀,缺省是:“VirtualTopic.Mirror.".
比如修改后的配置如下
<destinationInterceptors> <mirroredQueue copyMessage="true" postfix=".qmirror" prefix=""/> </destinationInterceptors>
在我的配置中并没有配置配置前缀,但是一定需要配置copyMessage="true",查看控制台如下
存在默认前缀的topic,此时只需要用一个消费者去消费该topic中的消息就可以了。
3. Per Destination Policies
ActiveMQ支持多种不同的策略,来单独配置每一个Destination,它的属性很多,可以参考官方文档:
http://activemq.apache.org/per-destination-policies.html
官方文档最后还给出了一个demo做参考。