How to force ActiveMQ connection to choose a broker for a new consumer randomly? - jms

I use the following url to create ActiveMQConnactionFactory:
failover:(tcp://server1:port,tcp://server2:port,tcp://server2:port)
What I want to do is to create multiple message consumers from this network of brokers.
The following is not a real code, but it helps to undestand how I do that:
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("BROKER_URL");
connection = connectionFactory.createConnection();
connection.start();
for (int i=0; i<10; i++) {
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
Destination queue = consumerSession.createQueue("QUEUE_NAME");
consumer = consumerSession.createConsumer(queue);
consumer.setMessageListener(new MessageListener());
}
The problem is that all consumers will be connected to one randomly choosen broker.
But I want them to be balanced over the network of brokers.
I believe it is possible to do that by creating multiple connections with the factory.
But what are the best practices for that?
And is this a good thing which I want? :)

Actually, the consumer would not be connected to a randomly chosen broker.
A connection is the part that connects to a broker. With the connection string you have provided, you will have ONE connection mapped to ONE randomly chosen broker. All consumers have their own sessions but these would use the same ONE connection to that ONE broker.
The only setting I know of, is that you can disable the randomize behavior of the failover protocol by setting ?randomize=false on the connection string. This would mean your connection will first try the first, then the second, then the third, etc.
But to achieve your requirement. I would make each consumer to have it's own connection. This, together with the randomize feature in the fail-over protocol would kinda load-balance the consumers; but not for real, there is no intelligence in there and is just "randomizing" the broker it is connecting to.
This means, I would do the following (from your code)
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("BROKER_URL");
for (int i=0; i<10; i++) {
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
Destination queue = consumerSession.createQueue("QUEUE_NAME");
consumer = consumerSession.createConsumer(queue);
consumer.setMessageListener(new MessageListener());
}
This way, each consumer will have it's own connection to "a" broker of your fail-over connection string
UPDATED AFTER QUESTION CHANGE:
If you want to let ActiveMQ randomly choose a broker for each consumer, the above mentioned solution is the way to go.
The best practice would be to put your consumers and producers as close to each other as possible. For this, I would recommend lowering the network consumer priority, so the local consumer and producer would have highest priority. Only when the local consumer is not idle, it would distribute further over the network to other consumers.
In addition to that, it will be a good idea if the operation on consumer side is long running to set a lower prefetch value, so that the messages do get load balanced around the network of brokers instead of one consumer snatching up 1,000 messages while other consumers are idle.

Related

How to ensure that JMSTemplate caches consumer i.e. com.ibm.mq.jms.MQQueueReceiver?

I am facing a scenario where the reply queue I connect to, runs out of handles. I have traced it to the fact that my JMS Producers are being cached but not my JMS consumers. I am able to send and receive messages just fine so there is no problem with connecting-sending-receiving to/from the queues. I am using the CachedConnectionFactory (SessionCacheSize = 10)with the target factory as com.ibm.mq.jms.MQQueueConnectionFactory while instantiating the jmsTemplate. Code snippet is as follows
:
:
String replyQueue = "MyQueue";// replyQueue which runs out of handles
messageCreator.setReplyToQueue(new MQQueue(replyQueue));
jmsTemplate.setReceiveTimeout(receiveTimeout);
jmsTemplate.send(destination, messageCreator);// Send to destination queue
Message message = jmsTemplate.receiveSelected(replyQueue,
String.format("JMSCorrelationID = '%s'", messageCreator.getMessageId()));
:
:
From the logs (jms TRACE is enabled) Producer is cached, so the destination queue "handle count" does not increase.
// The first time around (for Producer)
Registering cached JMS MessageProducer for destination[queue:///<destination>:com.ibm.mq.jms.MQQueueSender#c9x758b
// Second time around, the cached producer is reused
Found cached JMS MessageProducer for destination [queue:///<destination>]: com.ibm.mq.jms.MQQueueSender#c9x758b
However, the handles for the replyQueue keep increasing because for every call to that queue, I see a new JMS Consumer being registered. Ultimately the calls to open the replyQueue fail because of MQRC_HANDLE_NOT_AVAILABLE
// First time around
Registering cached JMS MessageConsumer for destination [queue:///<replyQueue>]:com.ibm.mq.jms.MQQueueReceiver#b3ytd25b
// Second time around, another MessageConsumer is registered !
Registering cached JMS MessageConsumer for destination [queue:///<replyQueue>]:com.ibm.mq.jms.MQQueueReceiver#re25b
My memory is a bit dim on this, but here is what is happening. You are receiving messages based on a message selector. This selector is always changing, however. As a test, either remove the selector or make it a constant and see what happens. So when you try to cache/pool based on connection/session/consumer, the consumer is always changing. This requires a new cache entry.
After you go through your 10 sessions, a new connection will be created, but the existing one is not closed. Increase your session count to 100, for example, and your connection count on the MQ broker should climb 10 time slower.
You need to create a new consumer for every message receive as your correlation ID is always changing. So just cache connection/session. No matter what you do, you will always have to round trip to the broker to ask for the new correlation ID.

querying artemis queue size fails

In a spring boot application using artemis we try to avoid queues containing too many messages. The intention is to only put in new messages if the number of messages currently in the queue falls below a certain limit, e.g. 100 messages. However, that seems not to work but we don't know why or what the "correct" method would be to implement that functionality. The number of messages as extracted by the code below is always 0 although in the gui there are messages.
To reproduce the problem I installed apache-artemis-2.13.0 locally.
We are doing something like the following
if (!jmsUtil.queueHasNotMoreElementsThan(QUEUE_ALMOST_EMPTY_MAX_AMOUNT, reprocessingMessagingProvider.getJmsTemplate())) {
log.info("Queue has too many messages. Will not send more...");
return;
}
jmsUtil is implemented like
public boolean queueHasNotMoreElementsThan(int max, JmsOperations jmsTemplate) {
return Boolean.TRUE.equals(
jmsTemplate.browse((session, queueBrowser) -> {
Enumeration enumeration = queueBrowser.getEnumeration();
return notMoreElemsThan(enumeration, max);
}));
}
private Boolean notMoreElemsThan(Enumeration enumeration, int max) {
for (int i = 0; i <= max; i++) {
if (!enumeration.hasMoreElements()) {
return true;
}
enumeration.nextElement();
}
return false;
}
As a check I used additionally the following method to give me the number of messages in the queue directly.
public int countPendingMessages(String destination, JmsOperations jmsTemplate) {
Integer totalPendingMessages = jmsTemplate.browse(destination,
(session, browser) -> Collections.list(browser.getEnumeration()).size());
int messageCount = totalPendingMessages == null ? 0 : totalPendingMessages;
log.info("Queue {} message count: {}", destination, messageCount);
return messageCount;
}
That method of extracting the queue size seems to be used as well by others and is based on the documentation of QueueBrowser: The getEnumeration method returns a java.util.Enumeration that is used to scan the queue's messages.
Would the above be the correct way on how to obtain the queue size? If so, what could be the cause of the problem? If not, how should the queue size be queried? Does spring offer any other possibility of accessing the queue?
Update: I read another post and the documentation but I wouldn't know on how to obtain the ClientSession.
There are some caveats to using a QueueBrowser to count the number of messages in the queue. The first is noted in the QueueBrowser JavaDoc:
Messages may be arriving and expiring while the scan is done. The JMS API does not require the content of an enumeration to be a static snapshot of queue content. Whether these changes are visible or not depends on the JMS provider.
So already the count may not be 100% accurate.
Then there is the fact that there may be messages still technically in the queue which have been dispatched to a consumer but have not yet been acknowledged. These messages will not be counted by the QueueBrowser even though they may be cancelled back to the queue at any point if the related consumer closes its connection.
Simply put the JMS API doesn't provide a truly reliable way to determine the number of messages in a queue. Furthermore, "Spring JMS" is tied to the JMS API. It doesn't have any other way to interact with a JMS broker. Given that, you'll need to use a provider-specific mechanism to determine the message count.
ActiveMQ Artemis has a rich management API that is accessible though, among other things, specially constructed JMS messages. You can see this in action in the "Management" example that ships with ActiveMQ Artemis in the examples/features/standard/management directory. It demonstrates how to use JMS resources and provider-specific helper classes to get the message count for a JMS queue. This is essentially the same solution as given in the other post you mentioned, but it uses the JMS API rather than the ActiveMQ Artemis "core" API.

Configuring a Dedicated Listener Container for each Queue using Spring AMQP Java Configuration

I have listeners configured in XML like this
<rabbit:listener-container connection-factory="connectionFactory" concurrency="1" acknowledge="manual">
<rabbit:listener ref="messageListener" queue-names="${address.queue.s1}" exclusive="true"/>
<rabbit:listener ref="messageListener" queue-names="${address.queue.s2}" exclusive="true"/>
<rabbit:listener ref="messageListener" queue-names="${address.queue.s3}" exclusive="true"/>
<rabbit:listener ref="messageListener" queue-names="${address.queue.s4}" exclusive="true"/>
<rabbit:listener ref="messageListener" queue-names="${address.queue.s5}" exclusive="true"/>
<rabbit:listener ref="messageListener" queue-names="${address.queue.s6}" exclusive="true"/>
</rabbit:listener-container>
I am trying to move that to Java Configuration and I don't see a way to add more than one MessageListener to a ListenerContainer. Creating multiple ListenerContainer beans is not an option in my case because I would not know the number of queues to consume from until runtime. Queue names will come from a configuration file.
I did the following
#PostConstruct
public void init()
{
for (String queue : queues.split(","))
{
// The Consumers would not connect if I don't call the 'start()' method.
messageListenerContainer(queue).start();
}
}
#Bean
public SimpleMessageListenerContainer messageListenerContainer(String queue)
{
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(consumerConnectionFactory);
container.setQueueNames(queue);
container.setMessageListener(messageListener());
// Set Exclusive Consumer 'ON'
container.setExclusive(true);
// Should be restricted to '1' to maintain data consistency.
container.setConcurrentConsumers(1);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return container;
}
It "sort" of works BUT I see some weird behavior with lots of ghost channels getting opened which never used to happen with the XML configuration. So it makes me suspicious that I am doing something wrong. I would like to know the correct way of creating MessageListenerContainers in Java configuration? Simply put, "How does Spring convert 'rabbit:listener-container' with multiple 'rabbit:listener' to java objects properly?" Any help/insight into this would be greatly appreciated.
Business Case
We have a Publisher that publishes User Profile Updates. The publisher could dispatch multiple updates for the same use and we have to process them in the correct order to maintain data integrity in the data store.
Example : User : ABC, Publish -> {UsrA:Change1,...., UsrA:Change 2,....,UsrA:Change 3} -> Consumer HAS to process {UsrA:Change1,...., UsrA:Change 2,....,UsrA:Change 3} in that order.
In our previous setup, we had 1 Queue that got all the User Updates and we had a consumer app with concurrency = 5. There were multiple app servers running the consumer app. That resulted in *5 * 'Number of instances of the consumer app' channels/threads* that could process the incoming messages. The speed was GREAT! but we were having out of order processing quite often resulting in data corruption.
To maintain strict FIFO order and still process message parallelly as much as possible, we implemented queue Sharding. We have a "x-consistent-hash with a hash-header on employee-id. Our Publisher publishes messages to the hash exchange and we have multiple sharded queues bound to the hash exchange. The idea is, we will have all changes for a given user (User A for example) queued up in the same shard. We then have our consumers connect to the sharded queues in 'Exclusive' mode and 'ConcurrentConsumers = 1' and process the messages. That way we are sure to process messages in the correct order while still processing messages parallelly. We could make it more parallel by increasing the number of shards.
Now on to the consumer configuration
We have the consumer app deployed on multiple app servers.
Original Approach:
I simply added multiple 'rabbit:listener' to my 'rabbit:listener-container' in my consumer app as you can see above and it works great except for the server that starts first get an exclusive lock on all the sharded queues and the other servers are just sitting there doing no work.
New Approach:
We moved the sharded queue names to the application configuration file. Like so
Consumer Instance 1 : Properties
queues=user.queue.s1,user.queue.s2,user.queue.s3
Consumer Instance 2 : Properties
queues=user.queue.s4,user.queue.s5,user.queue.s6
Also worth noting, we could have Any number of Consumer instances and the shards could be distributed unevenly between instances depending on resource availability.
With the queue names moved to configuration file, the XML confiugration will no longer work because we cannot dynamically add 'rabbit:listener' to my 'rabbit:listener-container' like we did before.
Then we decided to switch over to the Java Configuration. That is where we are STUCK!.
We did this initially
#Bean
public SimpleMessageListenerContainer messageListenerContainer()
{
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(consumerConnectionFactory);
container.setQueueNames(queues.split(","));
container.setMessageListener(messageListener());
container.setMissingQueuesFatal(false);
// Set Exclusive Consumer 'ON'
container.setExclusive(true);
// Should be restricted to '1' to maintain data consistency.
container.setConcurrentConsumers(1);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
container.start();
return container;
}
and it works BUT all our queues are on one connection sharing 1 channel. That is NOT good for speed. What we want is One connection and every queue gets its own channel.
Next Step
No success here YET!. The java configuration in my original question is where we are at now.
I am baffled why this is so HARD to do. Clearly the XML configuration does something that is NOT easly doable in Java confiugration (Or atleast it feel sthat way to me). I see this as a gap that needs to be filled unless I am compeltly missing something. Please correct me if I am wrong. This is a genuine business case NOT some ficticious edge case. Please feel free to comment if you think otherwise.
and it works BUT all our queues are on one connection sharing 1 channel. That is NOT good for speed. What we want is One connection and every queue gets its own channel.
If you switch to the DirectMessageListenerContainer, each queue in that configuration gets its own Channel.
See the documentation.
To answer your original question (pre-edit):
#Bean
public SimpleMessageListenerContainer messageListenerContainer1(#Value("${address.queue.s1}") String queue)
{
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(consumerConnectionFactory);
container.setQueueNames(queue);
container.setMessageListener(messageListener());
// Set Exclusive Consumer 'ON'
container.setExclusive(true);
// Should be restricted to '1' to maintain data consistency.
container.setConcurrentConsumers(1);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return container;
}
...
#Bean
public SimpleMessageListenerContainer messageListenerContainer6(#Value("${address.queue.s6}" ) String queue)
{
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(consumerConnectionFactory);
container.setQueueNames(queue);
container.setMessageListener(messageListener());
// Set Exclusive Consumer 'ON'
container.setExclusive(true);
// Should be restricted to '1' to maintain data consistency.
container.setConcurrentConsumers(1);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return container;
}
Here is the Java Configuration for creating SimpleMessageListenerContainer
#Value("#{'${queue.names}'.split(',')}")
private String[] queueNames;
#Bean
public SimpleMessageListenerContainer listenerContainer(final ConnectionFactory connectionFactory) {
final SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(queueNames);
container.setMessageListener(vehiclesReceiver());
setCommonQueueProperties(container);
return container;
}
Each <rabbit:listener > creates its own SimpleListenerContainer bean with the same ConnectionFactory. To do similar in Java config, you have to declare as much SimpleListenerContainer beans as you have queues: one for each of them.
You also may consider to use #RabbitListener approach instead: https://docs.spring.io/spring-amqp/docs/2.0.4.RELEASE/reference/html/_reference.html#async-annotation-driven

Spring AMPQ multiple consumers vs higher prefetch value

Even after reading plenty of SO questions (1,2) and articles, It is unclear on which is the better option to set for consumers. Multiple consumers or a higher prefetch value?
From what I understand, when it comes to SimpleRabbitListenerContainerFactory, as it was designed initially to have only one thread per connection it was designed to address a limitation that the amqp-client only had one thread per connection, does that mean that setting multiple consumers won't make much difference as there is only one thread that actually consumes from rabbit and than hands it off to the multiple consumers (threads)?
Or there are actually several consumers consuming at the same time?
So what is the best practice when it comes to spring implementation of rabbit concerning prefetch/consumers? When should one be used over the other? And should I switch to this new DirectRabbitListenerContainerFactory? Is it 'better' or just depends on the use case?
Some downsides I see when it comes to high prefetch is that maybe it can cause memory issues if an app consumes more messages that it can hold in the buffer? (haven't actually tested this yet, tbh)
And when it comes to multiple consumers, I see the downside of having more file descriptors opened on OS level and I saw this article about that each consumer actually pings rabbit for each ack and this making it slower.
FYI, if it is relevant, I usually have my config set up like this:
#Bean
public ConnectionFactory connectionFactory() {
final CachingConnectionFactory connectionFactory = new CachingConnectionFactory(server);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
connectionFactory.setVirtualHost(virtualHost);
connectionFactory.setRequestedHeartBeat(requestedHeartBeat);
return connectionFactory;
}
#Bean
public AmqpAdmin amqpAdmin() {
AmqpAdmin admin = new RabbitAdmin(connectionFactory());
admin.declareQueue(getRabbitQueue());
return admin;
}
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
final SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setConcurrentConsumers(concurrency);
factory.setMaxConcurrentConsumers(maxConcurrency);
factory.setPrefetchCount(prefetch);
factory.setMissingQueuesFatal(false);
return factory;
}
#Bean
public Queue getRabbitQueue() {
final Map<String, Object> p = new HashMap<String, Object>();
p.put("x-max-priority", 10);
return new Queue(queueName, true, false, false, p);
}
No; the SMLC wasn't "designed for one thread per connection" it was designed to address a limitation that the amqp-client only had one thread per connection so that thread hands off to consumer threads via an in-memory queue; that is no longer the case. The client is multi-threaded and there is one dedicated thread per consumer.
Having multiple consumers (increasing the concurrency) is completely effective (and was, even with the older client).
Prefetch is really to reduce network chatter and improve overall throughput. Whether you need to increase concurrency really is orthogonal to prefetch. You would typically increase concurrency if (a) your listener is relatively slow to process each message and (b) strict message ordering is not important.
The DirectListenerContainer was introduced to provide a different threading model, where the listener is invoked directly on the amqp-client thread.
The reasons for choosing one container over the other is described in Choosing a Container.
The following features are available with the SMLC, but not the DMLC:
txSize - with the SMLC, you can set this to control how many messages are delivered in a transaction and/or to reduce the number of acks, but it may cause the number of duplicate deliveries to increase after a failure. (The DMLC does have mesagesPerAck which can be used to reduce the acks, the same as with txSize and the SMLC, but it can’t be used with transactions - each message is delivered and ack’d in a separate transaction).
maxConcurrentConsumers and consumer scaling intervals/triggers - there is no auto-scaling in the DMLC; it does, however, allow you to programmatically change the consumersPerQueue property and the consumers will be adjusted accordingly.
However, the DMLC has the following benefits over the SMLC:
Adding and removing queues at runtime is more efficient; with the SMLC, the entire consumer thread is restarted (all consumers canceled and re-created); with the DMLC, unaffected consumers are not canceled.
The context switch between the RabbitMQ Client thread and the consumer thread is avoided.
Threads are shared across consumers rather than having a dedicated thread for each consumer in the SMLC. However, see the IMPORTANT note about the connection factory configuration in the section called “Threading and Asynchronous Consumers”.

Check if there are JMS messages on Glassfish 3 cluster nodes

I'm trying to check if there are messages on a queue. To access the queue I'm using a Connection Factory, a Destination Resource and JNDI lookup (method below). The problem comes when the Connection Factory points to multiple nodes from a Glassfish 3 cluster. The AddressList property has the list with ip addresses of the nodes.
One way to solve it, would be to check each node. Is there a way to read the value of the AddressList property from a JMS Connection Factory defined in the Glassfish resources ?
Are there any other ways to check if there are messages on a queue on all the nodes of a Glassfish 3 cluster ?
Wouldn't like to save the addresses of the cluster nodes both in a configuration file and in the JMS Connection Factory (the Connection Factory is already used in some other parts of the application).
Thanks.
private boolean existsMessagesOnTheQueue(String connectionFactoryName, Queue queue, String selector) throws Exception {
QueueConnectionFactory connectionFactory;
QueueConnection connection;
Session session ;
QueueBrowser browser ;
try {
connectionFactory = (QueueConnectionFactory) InitialContext.doLookup(connectionFactoryName);
connection = connectionFactory.createQueueConnection();
session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
browser = session.createBrowser(queue, selector);
connection.start();
if (browser.getEnumeration().hasMoreElements()) {
log.debug("Found message on " + queue.getQueueName());
return true;
}
} finally {
closeBrowser(browser);
closeSession(session);
closeConnection(connection);
}
return false;
}
Even if the connection factory lists multiple nodes in its AddressList attribute, it is sufficient to check for messages in any ONE of these addresses. All the other copies in the cluster would have the same messages.
From Oracle doc:
In a broker cluster, you create a physical destination on one broker and the cluster propagates it to all the others. Because the brokers cooperate to route messages across the cluster, client applications can consume messages from destinations on any broker in the cluster.
The above mentions about clients consuming messages from any broker in the cluster. So, Browsing could also be done through any broker.

Resources