0

I have researched so long regarding JMS load balancing. We can create multiple producer and mulitple consumer for load balancing JMS messages. But I want to understand, how we can load balance JMS messages with one producer and one consumer. I can not add more dependencies in my project like Apache Camel.

@Configuration
@EnableJms
@ComponentScan({"com.jmsloadbalance.jms"})
@Bean
public class JmsConfig {
public JmsTemplate getJmsTemplate() {
    JmsTemplate template = new JmsTemplate();
    template.setConnectionFactory(connectionFactory());
    template.setDefaultDestination(new ActiveMQQueue("default.topic");
    template.setExplicitQosEnabled(true);
    template.setDeliveryPersistent(false);
    template.setTimeToLive(60000);
    template.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
    template.setMessageConverter(getMessageConverter());
    return template;
}

@Bean
public DefaultJmsListenerContainerFactory defaultJmsListenerContainerFactory() {
    DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory());
    factory.setPubSubDomain(false);
    factory.setDestinationResolver(new DynamicDestinationResolver());
    factory.setConcurrency("1");
    factory.setMessageConverter(getMessageConverter());
    return factory;
}

private ActiveMQConnectionFactory connectionFactory() {
    ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory();
    factory.setBrokerURL("vm://localhost");
    return factory;
}

private MessageConverter getMessageConverter() {
    MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
    converter.setTypeIdPropertyName("JMSType");
    return converter;
}
}

This is my JmsConfig class where I can not make big configuration changes like introducing more JMSTemplate or more ConnectionFactory. My producer looks like below

@Service("accountJmsProducer")
public class AccountJmsProducer {

private static Logger LOG = Logger.getLogger(AccountJmsProducer.class);

@Autowired
private JmsTemplate template;

private Destination destination;

public Account create(Account account) {
    if (this.destination == null) {
        this.destination = new ActiveMQQueue("account.create");
    }
    template.convertAndSend(destination, account);
    return null;
}
}

My consumer looks like below:

@Service("accountJmsConsumer")
public class AccountJmsConsumer {

private static final Logger LOG = Logger.getLogger(AccountJmsConsumer.class);

@Autowired
@Qualifier("accountService")
private AccountService accountService;

private Account lastReceived;

@JmsListener(containerFactory = "defaultJmsListenerContainerFactory", destination = "account.create")
public Account create(Account account) {
    LOG.warn("Received " + account);
    setLastReceived(account);
    return accountService.create(account);
}
public synchronized Account getLastReceived() {
    return lastReceived;
}
public synchronized void setLastReceived(Account lastReceived) {
    this.lastReceived = lastReceived;
}
}
Sarvesh
  • 551
  • 1
  • 10
  • 25
  • I must be missing something. Your question makes no sense; how can you "load balance" across consumers, when there's only one consumer? By definition, he gets all the messages. – Gary Russell Jun 13 '18 at 13:10
  • @GaryRussell, I admit my question is not clear. I just want to say that I can not change JmsConfig class. But I am open for other changes if required. Is it possible if I create multiple Consumers but each consumer should recieve unique message only. One message should not be processed by multiple consumers. If this is possible, then It is possible to have load balancing across multiple consumers having one producer with above configuration. I want to know if this is possible, then how should I do it? – Sarvesh Jun 13 '18 at 14:03

1 Answers1

1

It's not clear what you mean by load balancing when there's one consumer, but based on your comment to my comment on your question:

As long as the destination is a queue (not a topic) and that is implied since you have factory.setPubSubDomain(false) then it will just work. It's part of the JMS contract. If there are multiple consumers on the same queue, messages will be distributed across those consumers; only one consumer will receive a particular message.

If a delivery fails, it may, or may not, be redelivered to the same consumer.

Most brokers (including ActiveMQ) offer some kind of prefetch mechanism. IIRC, with ActiveMQ it is 1000 by default. If you have fewer messages than that then one consumer might be idle; if so, reduce the prefetch to tune the distribution.

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • If I create muliple consumers then it will automatically load balanced within consumers. I think I should create another new class for my consumer like `@Service("accountJmsConsumerSecond") public class AccountJmsConsumerSecond`. Or I have to write another method like `@JmsListener(containerFactory = "defaultJmsListenerContainerFactory", destination = "account.create") public Account createSecond(Account account)` – Sarvesh Jun 13 '18 at 14:28
  • Why? Just set `factory.setConcurrency("2");` and you'll get 2 consumers. – Gary Russell Jun 13 '18 at 14:43