How to ensure multiple jms listener instances process messages only when currently executing listener instance acknowlege

648 Views Asked by At

I'm currently trying to build a messaging application using JMS Listener and IBM MQ, and I need to ensure that I can run two instances of the same listener at the same time. However, I want to make sure that the second instance waits until the first instance has fully processed and acknowledged the message. I'm using Spring Boot for my application, JMS Listener and IBM MQ.

Below is the my config class which is annotated with @component and @EnableJMS


    public class jmsconfig{

     @Bean
     public MQConnectionfactory getConnectionFactory(){
         MQConnectionFactory connectionFactory = new MQConnectionFactory();
         connectionFactory.setQueueManagerName("AA");
         connectionFactory.setExpirationTimeOut(3600000);
         connectionFactory.setPerformExpiration(true);
         connectionFactory.setPerformValidation(true);
         connectionFactory.setPerformOptimalSizeCheck(false);
         connectionFactory.setValidationTimeOut(180000);
         connectionFactory.setMinIdle(3);
         connectionFactory.setMaxIdle(5);
         reurn connectionFactory ;
    }
    
     @Bean(name="jmsListenerContainerFactory")
     public JmsListenerContainerFactory jmsListenerContainerFactory(MQConnectionfactory mqConnectionfactory){
         DefaultJmsListenerContainerFactory  containerFactory = new DefaultJmsListenerContainerFactory();
         containerFactory.setConnectionFactor(mqConnectionfactory);
         containerFactory.setSessionTransacted(true);
         containerFactory.setSessionAcknowledgementMode(Session.CLIENT_ACKNOWLEDE)
         reurn containerFactory ;
    }
    
}

Listener code:


     @Component
        public class Receiver {
          
          public sttaic int count = 0;
        
          @JmsListener(destination = "${inwardQueueName}", containerFactory = "jmsListenerContainerFactory")
          public void receiveMessage(javax.jms.Message message) throws javax.jms.JMSException {
            String messagetxt = "";
            OrderObject order = null;
            if (message instanceof javax.jms.TextMessage) {
              messagetxt = ((TextMessage)message).getText();
              OrderObject order = //code to covert messagetxt to object
              System.out.println("Message pciked up with Order Id : " +order.getOrderId)
              TimeUnit.SECONDS.sleep(15);
              count++;
             if(order.getOrderId==1){
              if(count<=4){
                throw new Exception("Exception occurred");
              }
            }
            }
            TimeUnit.SECONDS.sleep(15);
            System.out.println("Message Acknowledged for orderId: " +order.getOrderId)
            message.acknowledge();
          }
        }

I start the first instance and pushes first message to MQ with orderId 1. It prints the below statement and then waits for 15 seconds

Message picked up with Order Id : 1

Immediately, I start the second instance and then pushes second message to MQ with orderId 2. I can see the below output on the console.

Message picked up withorderId 2
Message Acknowledged for orderId: 2

After some time, when the first instance completes(since there is a wait of 15 sec), I can see the below output.

Message pciked up with Order Id : 1
Message pciked up with Order Id : 1
Message pciked up with Order Id : 1
Message Acknowledged for orderId: 1

With the above output, the Second instance is picking up second message from the MQ in parallel and process them while the first instance is still processing the first message.

Can anyone help with what is wrong with the above implementation?

1

There are 1 best solutions below

0
chughts On

Based on your description it looks like message 2 shouldn't be processed until the actions needed for message 1 have completed, once they have then it doesn't matter which listener works on message 2. Sequencing is key and not which listener does the processing.

To do this, message 2 needs to be tied into message 1.

One way would be for the producer app not to place message 2 on the queue until the processing for message 1 has been approved.

Another way would be to tie message 2 with message 1 (maybe via specific queues or via properties, or via priority, or a permutation of these) such that they are always read as a pair and as part of a transaction.

IE.

  • Start Transaction
  • Ensure both messages are read together, making sure that some other process, or listener can't or doesn't see message 2. Only the @JMSListener that receives message 1 would know how to obtain message 2, which it does using normal JMS Get logic.
  • Process message 1
  • Process message 2
  • Commit, so both messages can be removed.

You could commit between message 1 & 2, but if there is a failure, how do you detect that message 1 was processed so you can process message 2. Logic to start processing from message 2 gets very complicated if you commit here.