Load Balancing with JMS Queue
Some time ago we were implementing Identity Management solution at an electricity distribution company. The core component was our Identity Manager CzechIdM.
CzechIdM interconnects systems with portal application and enables hundreds of thousands of customers to create and manage their login information. Because of the huge number of users, CzechIdM has to withstand a significant load of hundreds of requests per second. In this post, we will show you, how it is done.
Introduction
Every information system and hardware has its limits. In the complicated application, not mentioning environment of integration platform, the limitations are even stricter. Aside of RAM consumption, disk space and CPU, we have to deal with network throughput, database connectivity and endpoint system delay.
In this deployment, every single user request is important and must be served. This is because of registration, password reset and password change functions. Because of the high load, CzechIdM implements request queue. Under fire, the system with the queue may slow down, but every user request will be processed. And that is what matters.
JMS Technology
Java comes with bundled technology for internal application messaging called Java Messaging Services, JMS. Using internal messaging, some part of the application can transfer data to another part. The JMS messages are stored in the queue until the recipient accepts them. The queue can be either persistent or in-memory.
In our case, user requests are served by classes, which simply encapsulate request into message, push it to the queue and wait for the response from component, which actually processes the request.
Queues themselves are handled by the application server. Thanks to this, configuring new queue is only a matter of creating appropriate xml configuration.
First of all, we define new sender in components.xml:
<jms:managed-queue-sender name="myQueueSender" auto-create="true" queue-jndi-name="queue/myQueue"/>
Then, we will define queue service itself:
<?xml version="1.0" encoding="UTF-8"?>
<server>
<mbean
code="org.jboss.jms.server.destination.QueueService"
name="jboss.messaging.destination:service=Queue,name=myQueue"
xmbean-dd="xmdesc/Queue-xmbean.xml">
<depends optional-attribute-name="ServerPeer">jboss.messaging:service=ServerPeer</depends>
<depends>jboss.messaging:service=PostOffice</depends>
</mbean>
</server>
Finally, we will create a Java class which sends the messages into queue:
@Name(TaskMultithreadController.COMPONENT_NAME)
@Stateless
public class TaskMultithreadControllerBean implements TaskMultithreadController {
@In("myQueueSender")
protected QueueSender queueSender;
@In(create = true)
protected QueueSession queueSession;
public Object executeTask(ApplicationTask task, int priority, boolean waitForResponse) {
try {
//prepare a message for send
ObjectMessage message = this.queueSession.createObjectMessage();
TemporaryQueue tempQueue = null;
MessageConsumer consumer = null;
//in case we are expecting response
if (waitForResponse) {
tempQueue = this.queueSession.createTemporaryQueue();
consumer = this.queueSession.createConsumer(tempQueue);
message.setJMSReplyTo(tempQueue);
}
message.setObject(task);
this.queueSender.send(message, DeliveryMode.PERSISTENT, priority, timeout);
...
//we are waiting for response - we create temporary reverse queue
if (waitForResponse) {
ObjectMessage response = (ObjectMessage) consumer.receive(waitForResponseTimeout);
Object result = null;
//we return the response
if (response != null) {
result = response.getObject();
}
return result;
} else {
return null;
}
...
}
Message Driven Bean
The queue has attached listeners. These listeners consume messages from the queue and process data. They are generally called Message Driven Beans. Their number is limited by the settings in the application server, so we are sure we will not tun out of memory due to high message count.
Message Driven Bean is a Java class which implements MessageListener interface and is appropriately anotated:
@Name("myMDBean") @Depends( {"jboss.web.deployment:war=/idm"} ) @MessageDriven( name = "myMDBean", activationConfig = { @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/myQueue"), @ActivationConfigProperty(propertyName = "maxSession", propertyValue = "10")} ) public class TaskMultithreadExecutorBean implements MessageListener { @In(create = true) protected QueueSession queueSession; public void onMessage(Message message) { try { //do we need to send reply somewhere? Destination replyTo = message.getJMSReplyTo(); Integer timeout = null; //get timeout reply if (replyTo != null) { replyProducer = queueSession.createProducer(null); timeout = IdMConfiguration.getInstance().getAsInteger("queueResponseTimeout"); } ApplicationTask task = message.getObject(); ... //send reply if (replyTo != null) { ObjectMessage response = queueSession.createObjectMessage(); response.setObject(result); replyProducer.send(replyTo, response, DeliveryMode.PERSISTENT, 9, timeout); } ... } catch (Throwable e) { ... } } }
The most important method of the Bean is onMessage() method. Input of this method is whole message. Only problem here is, that message payload has to be a primitive type (or a String). As a workaround, we used standard Java serialization combined with base64 encoding – we send every request (or response) object in its serialized base64 encoded form.
Maximum number of Message Driven Beans
There can exists a pool of Message Driven Beans. Application server creates a number of threads dedicated to process requests incoming to the pool. The pool size is configurable throught the annotation:
@ActivationConfigProperty(propertyName = "maxSession", propertyValue = "10")
And thats it! The JMS technology takes care of everything else – thread scheduling, pool synchronization or even the message persistence in the database.
Conclusion
In the post, we described how CzechIdM uses JMS for load balancing of user requests. If you have any questions, don’t hesistate and contact us on info@bcvsolutions.eu.