Virtual Destinations allow us to create logical destinations that clients can use to produce and consume from but which map onto one or more physical destinations. It allows us to provide more flexible loosely coupled messaging configurations.
The idea behind publish subscribe is a great one. Allow producers to be decoupled from consumers so that they do not even know how many consumers are interested in the messages they publish. The JMS specification defines support for durable topics however they have limitations as we will describe…
The limitations of JMS durable topics
A JMS durable subscriber MessageConsumer is created with a unique JMS clientID and durable subscriber name. To be JMS compliant only one JMS connection can be active at any point in time for one JMS clientID, and only one consumer can be active for a clientID and subscriber name. i.e., only one thread can be actively consuming from a given logical topic subscriber. This means we cannot implement
- load balancing of messages.
- fast failover of the subscriber if that one process running that one consumer thread dies.
Now queue semantics in JMS offer the ability to load balance work across a number of consumers in a reliable way - allowing many threads, processes and machines to be used to process messages. Then we have sophisticated sticky load balancing techniques like Message Groups to load balance and parallelise work while maintaining ordering.
Another added benefit of having physical queues for each logical topic subscriber is we can them monitor the queue depths via JMX to monitor system performance together with being able to browse these physical queues.
Virtual Topics to the rescue
The idea behind virtual topics is that producers send to a topic in the usual JMS way. Consumers can continue to use the Topic semantics in the JMS specification. However if the topic is virtual, consumer can consume from a physical queue for a logical topic subscription, allowing many consumers to be running on many machines & threads to load balance the load.
E.g., let’s say we have a topic called VirtualTopic.Orders. (Where the prefix VirtualTopic. indicates its a virtual topic). And we logically want to send orders to systems A and B. Now with regular durable topics we’d create a JMS consumer for clientID_A and “A” along with clientID_B and “B”.
With virtual topics we can just go right ahead and consume to queue Consumer.A.VirtualTopic.Orders to be a consumer for system A or consume to Consumer.B.VirtualTopic.Orders to be a consumer for system B.
We can now have a pool of consumers for each system which then compete for messages for systems A or B such that all the messages for system A are processed exactly once and similarly for system B.
Customizing the out-of-the-box defaults
The out-of-the-box defaults are described above. Namely that the only virtual topics available must be within the VirtualTopic.> namespace and that the consumer queues are named Consumer.*.VirtualTopic.>.
You can configure this to use whatever naming convention you wish. The following example shows how to make all topics virtual topics. The example below is using the name > to indicate ‘match all topics’. You could use this wildcard to apply different virtual topic policies in different hierarchies.
<destinationInterceptors> <virtualDestinationInterceptor> <virtualDestinations> <virtualTopic name=">" prefix="VirtualTopicConsumers.*." selectorAware="false"/> </virtualDestinations> </virtualDestinationInterceptor> </destinationInterceptors>
Note that making a topic virtual does add a small CPU overhead when sending messages to the topic but it is fairly small.
|selectorAwae||false||only messages that match one of the existing subscribers are actually dispatched. Using this option prevents the build up of unmatched messages when selectors are used by exclusive consumers|
|local||false||when true, don’t fan out messages that were received over a network|
|concurrentSend||false||when true, use an executor to fanout such that sends occur in parallel. This allows the journal to batch writes which will reduce disk io (5.12)|
|transactedSend||false||when true, use a transaction for fanout sends such that there is a single disk sync. A local broker transaction will be created if there is no client transaction (5.13)|
|dropOnResourceLimit||false||when true, ignore any ResourceAllocationException thrown during fanout (see: sendFailIfNoSpace policy entry) (5.16)|
|setOriginalDestination||true||when true, the destination on the forwarded message is set to the consumer queue and the originalDestination message property tracks the virtual topic (5.16)|
When selectorAware=true, only active consumers are condidered for selector matching. If consumers disconnect and reconnect they will miss messages. The intent of selectorAware=true is to not have messages build up. The virtualSelectorCacheBrokerPlugin provides a cache that tracks the selectors associated with a destination by a consumers such that they can apply in the absense of that consumer. In this way the just the selected messages build up. The existing set of selectors can be persisted such that it can be recovered on restart. the plugin is applied in the normal way to the plugins section. Code Block
<plugins> <virtualSelectorCacheBrokerPlugin persistFile="<some path>/selectorcache.data" /> </plugins>
Note: the persistFile option uses java serialisation that should be locked down with an appropriate jdk.serialFilter that allows ConcurrentHashMap
Composite Destinations allow for one-to-many relationships on individual destinations; the main use case is for composite queues. For example when a message is sent to queue A you may want to forward it also to queues B and C and topic D. Composite destinations are then a mapping from a virtual destination to a collection of other physical destinations. In this case the mapping is broker side and the client is unaware of the mapping between the destinations. This is different from client side Composite Destinations where the client uses a URL notation to specify the actual physical destinations that a message must be sent to.
The following example shows how to set up a
MY.QUEUE then it is really forwarded to the physical queue
FOO and the topic
<destinationInterceptors> <virtualDestinationInterceptor> <virtualDestinations> <compositeQueue name="MY.QUEUE"> <forwardTo> <queue physicalName="FOO" /> <topic physicalName="BAR" /> </forwardTo> </compositeQueue> </virtualDestinations> </virtualDestinationInterceptor> </destinationInterceptors>
By default, subscribers cannot consume messages directly from a composite queue or topic - it is a logical construct only. Given the configuration above, subscribers can only consume messages from
BAR; but not
This behaviour can be altered to implement use cases such as watching a queue by sending the same messages to a notification topic (wire tapping), by setting the optionally set
forwardOnly attribute to false.
<compositeQueue name="IncomingOrders" forwardOnly="false"> <forwardTo> <topic physicalName="Notifications" /> </forwardTo> </compositeQueue>
Messages sent to
IncomingOrders will all be copied and forwarded to
Notifications, before being placed on the physical
IncomingOrders queue for consumption by subscribers.
forwardOnly attribute is not defined or is set to
true, there is no logical difference between a
compositeQueue and a
compositeTopic - they can be used interchangeably. It is only when a composite destination is made physical through the use of
forwardOnly that the choice of
compositeQueue has an impact on behavior.
Using filtered destinations
From Apache ActiveMQ 4.2 onwards you can now use selectors to define virtual destinations.
You may wish to create a virtual destination which forwards messages to multiple destinations but applying a selector first to decide if the message really does have to go to a particular destination.
The following example shows how a message sent to the virtual destination MY.QUEUE will be forwarded to FOO and BAR if the selectors match
<destinationInterceptors> <virtualDestinationInterceptor> <virtualDestinations> <compositeQueue name="MY.QUEUE"> <forwardTo> <filteredDestination selector="odd = 'yes'" queue="FOO"/> <filteredDestination selector="i = 5" topic="BAR"/> </forwardTo> </compositeQueue> </virtualDestinations> </virtualDestinationInterceptor> </destinationInterceptors>
Avoiding Duplicate Message in a Network of Brokers
TLDR: bridge consumer queues or virtual topics, not both.
Typically you would network consumer queues. In this case it is important to not bridge any normal topic consumer on the virtual topic because any forwarded message would again get fanned out to consumer queues on the networked broker, leading to duplicates.
It is also possible to bridge the virtual topic in which case it is necessary exclude the consumer queues from any network connector configuration.
Here is an example of how to exclude virtual topic consumer queues:
<networkConnectors> <networkConnector uri="static://([tcp://localhost:61617](tcp://localhost:61617))"> <excludedDestinations> <queue physicalName="Consumer.*.VirtualTopic.>"/> </excludedDestinations> </networkConnector> </networkConnectors>