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名称不同(前缀不同),所以不同的应用中都可以接收到全部的消息。每个客户端相当于一个持久订阅者,而且这个客户端可以使用多个消费者共同来承担消费任务。
生产者:
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();
}
}
消费者:
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();
}
}
组合列队Composite Destinations
组合队列允许用一个虚拟的destination代表多个destinations。这样就可以通过composite destinations在一个操作中同时向多个queue发送消息。
客户端实现的方式
在composite destinations中,多个destination之间采用“,”分割。例如:
Queue queue = new ActiveMQQueue("FOO.A,FOO.B,FOO.C");
或
Destination destination = session.createQueue("my-queue,my-queue2");
如果你希望使用不同类型的destination,那么需要加上前缀如queue:// 或topic://,例如:
Queue queue = new ActiveMQQueue("FOO.A,topic://NOTIFY.FOO.A");
在conf/activemq.xml中的broker下配置实现
<destinationInterceptors>
<virtualDestinationInterceptor>
<virtualDestinations>
<compositeQueue name="MY.QUEUE">
<forwardTo>
<queue physicalName="my-queue" />
<queue physicalName="my-queue2" />
</forwardTo>
</compositeQueue>
</virtualDestinations>
</virtualDestinationInterceptor>
</destinationInterceptors>
再java代码发送的时候,队列的的名字就用MY.QUEUQ
Configure Startup Destinations
如果需要在ActiveMQ启动的时候,创建Destination的话,可以如下配置conf/activemq.xml的broker下:
<destinations>
<queue physicalName="FOO.BAR" />
<topic physicalName="SOME.TOPIC" />
</destinations>
Delete Inactive Destinations
一般情况下,ActiveMQ的queue在不使用之后,可以通过web控制台或是JMX方式来删除掉。当然,也可以通过配置,使得broker可以自动探测到无用
的队列(一定时间内为空的队列)并删除掉,回收响应资源。可以如下配置conf/activemq.xml:
<broker schedulePeriodForDestinationPurge="10000">
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry queue=">" gcInactiveDestinations="true" inactiveTimoutBeforeGC="30000"/>
</policyEntries>
</policyMap>
</destinationPolicy>
</broker>
说明:
schedulePeriodForDestinationPurge:设置多长时间检查一次,这里是10秒,默认为0
inactiveTimoutBeforeGC:设置当Destination为空后,多长时间被删除,这里是30秒,默认为60
gcInactiveDestinations: 设置删除掉不活动队列,默认为false
Destination Options
队列选项是给consumer在JMS规范之外添加的功能特性,通过在队列名称后面使用类似URL的语法添加多个选项。包括:
1:consumer.prefetchSize,consumer持有的未确认最大消息数量,默认值 variable
2:consumer.maximumPendingMessageLimit:用来控制非持久化的topic在存在慢消费者的情况下,丢弃的数量,默认0
3:consumer.noLocal :默认false
4:consumer.dispatchAsync :是否异步分发 ,默认true
5:consumer.retroactive:是否为回溯消费者 ,默认false
6:consumer.selector:Jms的Selector,默认null
7:consumer.exclusive:是否为独占消费者 ,默认false
8:consumer.priority:设置消费者的优先级,默认0
使用示例:
queue = new ActiveMQQueue("TEST.QUEUE?consumer.dispatchAsync=false&consumer.prefetchSize=10");
consumer = session.createConsumer(queue);