5 min read

(For more resources related to this topic, see here.)

Getting ready

For this article we will use the sample application virtual-destinations to demonstrate the use of Virtual Destinations.

How to do it…

To run the sample for this article, open a terminal, change the path to point to the directory where virtual-destinations is located, and run it by typing mvn compile exec:java.

In the terminal where you started the example, you will see output similar to the following snippet, indicating that the application is running:

Starting Virtual Destination example now…
Queue A Consumer 1 processed 500 Messages
Queue A Consumer 2 processed 500 Messages
Queue B Consumer processed 1000 Messages
Finished running the Virtual Destination example.


How it works…

Our example takes advantage of ActiveMQ’s Virtual Destination feature to allow a JMS Producer to send messages to a topic and have those messages be received by a number of queue consumers. Let’s take a look at the sent code first:

private void sendMessages() throws Exception {
Connection connection =
connectionFactory.createConnection();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
Destination destination =
session.createTopic(“VirtualTopic.Foo”);
MessageProducer producer =
session.createProducer(destination);
for (int i = 0; i < 1000; ++i) {
producer.send(session.createMessage());
}
connection.close();
}


As we can see, there’s not much new here; we are just using standard JMS API code to send our messages. The important part is in the name of the topic destination VirtualTopic.Foo, which informs ActiveMQ Broker we want this topic to be one of those special Virtual Destinations.

Now let’s take a look at the consumer code, and then we’ll try and make sense of how this works:

Queue queueA =
receiverSession.createQueue(“Consumer.A.
VirtualTopic.Foo”);
VirtualMessageListener listenerA1 = new
VirtualMessageListener(done);
MessageConsumer consumerA1 =
receiverSession.createConsumer(queueA);
consumerA1.setMessageListener(listenerA1);
VirtualMessageListener listenerA2 = new
VirtualMessageListener(done);
MessageConsumer consumerA2 =
receiverSession.createConsumer(queueA);
consumerA2.setMessageListener(listenerA2);

 Queue queueB =
receiverSession.createQueue(“Consumer.B.
VirtualTopic.Foo”);
VirtualMessageListener listenerB = new
VirtualMessageListener(done);
MessageConsumer consumerB =
receiverSession.createConsumer(queueB);
consumerB.setMessageListener(listenerB);


As we can see, the consumer code is also not very mysterious; we create two consumers on the Consumer.A.VirtualTopic.Foo queue and one on the Consumer.B.VirtualTopic.Foo queue, and yet when we run the example, the two consumers on queue A split 500 of the topic message and the consumer on queue B gets its own copy of the 1,000 messages. So what’s happening on the broker then to make this happen?

By default, whenever a topic is created with a name matching the pattern VirtualTopic.<TopicName>, we gain access to the feature known as Virtual Destinations. These Virtual Destinations allow us to send a message to a topic but create consumers that have all the benefits of queue consumers, namely, those of load balancing and message persistence. Our queue consumers just need to use their own naming convention to access the Virtual Destination functionality. Each queue that we want to create must be named using the pattern Consumer.<Name>.VirtualTopic.<TopicName>.

In the following figure we can see a visual representation of the message flow in our example:

As messages are sent to our example’s topic, they are distributed to both the queues we created. Two of our consumers split the work of processing messages from queue A, while the other consumer gets all the messages from queue B. In our example we didn’t attach a listener to the topic itself, but we could have done so and it would also have received all the messages we sent. It’s easy to imagine using the topic as a way to monitor the messages that are being distributed to the queue consumers in this scenario; this pattern is often referred to as a wiretap.

If it isn’t already apparent, Virtual Destinations are a very powerful feature for your messaging applications. Topics are great for broadcasting events but when we need to be able to consume messages that were sent while a client was offline, the only recourse is to use a durable topic subscription. Unfortunately, durable subscriptions have a number of limitations, the least of which is that only one JMS connection can be actively subscribed to a logical topic subscription. This means that we can’t load balance messages and we can’t have fast failover if a subscriber goes down. Virtual Destinations solve these problems since all of our consumers subscribe to a queue so their messages are persistent, and the work of processing the messages on the queue can be shared by more than one active subscription.

There’s more…

The naming pattern for Virtual Destinations is not set in stone. By default, ActiveMQ is configured to use the pattern we used in our sample application, but you can easily change this. In the following XML snippet, we configured our broker to all topics on our broker into virtual topics. We use the wildcard syntax here, which matches every topic name a client sends messages to:

<broker>
<destinationInterceptors>
<virtualDestinationInterceptor>
<virtualDestinations><virtualTopic name=”>” prefix=”VirtualTopic
Consumers.*.”/>
</virtualDestinations></virtualDestinationInterceptor>
</destinationInterceptors>
</broker>


More information on Virtual Destinations can be found on the ActiveMQ website, http://activemq.apache.org/virtual-destinations.html.

Summary

This article thus explained what Virtual Destinations are and how to use them to avoid the limitations of JMS’s durable topic subscriptions.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here