Enterprise Java

Load balancing with Apache Camel

In this example we will show you how to use Apache Camel as a load balancer for your system. In computer world a load balancer is a device that acts as a reverse proxy and distributes network or application traffic across a number of servers. Load balancers are used to increase capacity (concurrent users) and reliability of applications. With the help of Camel we can make our own software load balancer in no time. Enjoy riding!

Load balancing is not a separate pattern in Enterprise Integration Patterns book (as Claus Ibsen said it would be if there was a second edition of the book) but Camel treats it just like another EIP. While Camel syntax makes load balancing looks deceivingly easy it’s still a complicated topic and you should spend some time planning a suitable strategy in design phase. Camel comes with a number of built-in load balancing policies. In this example we cover some of them that are more commonly used.

The codes for this article use Maven 3.3.9, Eclipse Mars 4.5.0 and Apache Camel 2.17.1. Please notice the load balancer API has changed a bit since version 2.15, if you are going to use earlier versions of Camel please consult the documentation. All the classes in this example source code use @Test annotated method, so you have to run them as JUnit tests and hopefully see the green bar.

1. Creating base project

Before we move on to writing actual codes lets first create a Maven project in Eclipse. First select File -> New… -> new project. Type maven and select Maven project.

Create a new maven project
Create a new maven project

In the next window check the create a simple project option and click next:

Create a simple maven project
Create a simple maven project

Finally add the following configuration and click finish:

Configure maven project
Configure maven project

Now edit the pom.xml file to look as follow:

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.javacodegeeks</groupId>
<artifactId>camelLoadBalancer</artifactId>
<version>1.0.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-core</artifactId>
            <version>2.17.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-test</artifactId>
            <version>2.17.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
</project>

2. Load balancing

We mentioned different policies that Camel support but what are policies? A policy defines how workload is distributed among different receivers (processor, consumer service…). In this article we sow examples of Random, round robin and their weighted counterparts and topic policies. At the end we show you how to make your own policy.

2.1. Random

The simplest form of load balancing policy is random. As the name implies you just define the endpoints that should handle the load and Camel decides which one to use, randomly. Here is the code that implements random policy. m1, m2 and m3 are the endpoints that handle the message. The end always receives the message but each time you run the class one of the aforementioned end points is used. This selection is totally random and you can check this randomness by adding your own assertions.

RandomLoadBalance.java

package com.jcg;

import org.apache.camel.EndpointInject;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Test;

public class RandomLoadBalance extends CamelTestSupport{
	@EndpointInject(uri="mock:m1")
	MockEndpoint m1;
	@EndpointInject(uri="mock:m2")
	MockEndpoint m2;
	@EndpointInject(uri="mock:m3")
	MockEndpoint m3;
	@EndpointInject(uri="mock:end")
	MockEndpoint end;

	@Override
	protected RouteBuilder createRouteBuilder() throws Exception{
		return new RouteBuilder(){
			@Override
			public void configure() throws Exception {
				from("direct:start").loadBalance().random().to(m1,m2,m3).end().to(end);
			}
		};
	}
	
	@Test
	public void testSending() throws Exception{
		end.expectedMessageCount(1);
		template.sendBody("direct:start", "");
		end.assertIsSatisfied();
	}
}

2.2. Round robin

Round robin is another simple type of load balancing. In this policy the endpoints are used in turn one by one. In the example code you see the message goes through m1, m2, m3 and again m1.

RoundRobinLoadBalance.java

package com.jcg;

import javax.naming.Context;
import org.apache.camel.EndpointInject;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.dataset.SimpleDataSet;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Test;

public class RoundRobinLoadBalance extends CamelTestSupport{
	
	@EndpointInject(uri="mock:m1")
	MockEndpoint m1;
	@EndpointInject(uri="mock:m2")
	MockEndpoint m2;
	@EndpointInject(uri="mock:m3")
	MockEndpoint m3;
	@EndpointInject(uri="mock:end")
	MockEndpoint end;
	
	@Override
	protected RouteBuilder createRouteBuilder() throws Exception{
		return new RouteBuilder(){
			@Override
			public void configure() throws Exception {
				from("dataset:start").loadBalance().roundRobin().to(m1,m2,m3).end().to(end);
			}
		};
	}
	
	@Override
	protected Context createJndiContext() throws Exception{
		SimpleDataSet sds = new SimpleDataSet();
		sds.setSize(4);
		Context context = super.createJndiContext();
		context.bind("start", sds);
		return context;
	}
	
	@Test
	public void testSending() throws Exception{	
		m1.expectedMessageCount(2);
		m2.expectedMessageCount(1);
		m3.expectedMessageCount(1);
		end.expectedMessageCount(4);
		template.sendBody("dataset:start", "");
		m1.assertIsSatisfied();
		m2.assertIsSatisfied();
		m3.assertIsSatisfied();
		end.assertIsSatisfied();
	}
}

2.3. Weighted round robin

In real world it rarely happens that you have some identical machines serving your requests. So it makes sense to have one machine that is probably more powerful does more job than others. Weighted round robin and weighted random are more sophisticated counterparts of round robin and random policies respectively. With weighted round robin (or random) you can control load balancing in a fine-grained manner. Here we give an example of weighted round robin. Weighted random is identical.

The weight method has two arguments. First is a boolean that defines if policy is round robin (true) or random (false). The second argument is a String that defines distribution ratio of the corresponding endpoints. Here “2,1” means m1 receives twice as much traffic as m2 receives. In earlier version of Camel you had to use a List of integers.

WeightedRoundRobinLoadBalance.java

package com.jcg;

import javax.naming.Context;
import org.apache.camel.EndpointInject;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.dataset.SimpleDataSet;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Test;

public class WeightedRoundRobinLoadBalance extends CamelTestSupport{
	
	@EndpointInject(uri="mock:m1")
	MockEndpoint m1;
	@EndpointInject(uri="mock:m2")
	MockEndpoint m2;
	@EndpointInject(uri="mock:end")
	MockEndpoint end;
	
	@Override
	protected RouteBuilder createRouteBuilder() throws Exception{
		return new RouteBuilder(){
			@Override
			public void configure() throws Exception {
				// first argument of weighted method is a boolean defines if the policy is
				// Round robin (true) or Random (false)
				from("dataset:start").loadBalance().weighted(true,"2,1").to(m1,m2).end().to(end);
			}
		};
	}
	
	@Override
	protected Context createJndiContext() throws Exception{
		SimpleDataSet sds = new SimpleDataSet();
		sds.setSize(6);
		Context context = super.createJndiContext();
		context.bind("start", sds);
		return context;
	}
	
	@Test
	public void testSending() throws Exception{
		m1.expectedMessageCount(4);
		m2.expectedMessageCount(2);
		end.expectedMessageCount(6);
		template.sendBody("dataset:start", "");
		m1.assertIsSatisfied();
		m2.assertIsSatisfied();
		end.assertIsSatisfied();
	}
}

2.4. Topic

Topic is fundamentally different from other policies because all the end points receive the message. In our example code m1, m2 and m3 all process 5 messages and the end endpoint receives 5 messages too. Topic can be useful in guarding against endpoint failures.

TopicLoadBalance.java

package com.jcg;

import javax.naming.Context;
import org.apache.camel.EndpointInject;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.dataset.SimpleDataSet;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Test;

public class TopicLoadBalance extends CamelTestSupport{
	
	@EndpointInject(uri="mock:m1")
	MockEndpoint m1;
	@EndpointInject(uri="mock:m2")
	MockEndpoint m2;
	@EndpointInject(uri="mock:m3")
	MockEndpoint m3;
	@EndpointInject(uri="mock:end")
	MockEndpoint end;
	
	@Override
	protected RouteBuilder createRouteBuilder() throws Exception{
		return new RouteBuilder(){
			@Override
			public void configure() throws Exception {
				from("dataset:start").loadBalance().topic().to(m1,m2,m3).end().to(end);
			}
		};
	}
	
	@Override
	protected Context createJndiContext() throws Exception{
		SimpleDataSet sds = new SimpleDataSet();
		sds.setSize(5);
		Context context = super.createJndiContext();
		context.bind("start", sds);
		return context;
	}
	
	@Test
	public void testSending() throws Exception{	
		m1.expectedMessageCount(5);
		m2.expectedMessageCount(5);
		m3.expectedMessageCount(5);
		end.expectedMessageCount(5);
		template.sendBody("dataset:start", "");
		m1.assertIsSatisfied();
		m2.assertIsSatisfied();
		m3.assertIsSatisfied();
		end.assertIsSatisfied();
	}
}

2.5. Custom load balancer

Camel offers many useful policies but there are always times when none of them fulfill your needs. In such situations you have to define your own load balancing strategy and still Camel doesn’t leave you alone. Custom load balancer lets you define your own policy easily. In the example code we define a load balancer that checks the message header “sessionID” field. If it is even it sends it to m1, if odd to m2. Of course this is an unrealistic example but we deliberately made it simple to be able to focus on load balancing implementation free of business logic clutter. First we make a class that extends LoadBalancerSupport class and override process method. Then we pass an instance of this class to loadBalance method.

SessionChecker.java

package com.jcg;

import org.apache.camel.AsyncCallback;
import org.apache.camel.Exchange;
import org.apache.camel.processor.loadbalancer.LoadBalancerSupport;

public class SessionChecker extends LoadBalancerSupport{

	@Override
	public boolean process(Exchange exchange, AsyncCallback callback) {
		
		int id = exchange.getIn().getHeader("sessionID", Integer.class);
		try{
			if(id%2 == 0){
				getProcessors().get(0).process(exchange);
			} else{
				getProcessors().get(1).process(exchange);
			}
		}catch(Exception e){
			e.printStackTrace();
		}
		callback.done(true);
		return true;
	}
}

CustomLoadBalance.java

package com.jcg;

import java.util.HashMap;
import java.util.Map;
import javax.naming.Context;
import org.apache.camel.EndpointInject;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.dataset.SimpleDataSet;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Test;

public class CustomLoadBalance extends CamelTestSupport{
	
	@EndpointInject(uri="mock:m1")
	MockEndpoint m1;
	@EndpointInject(uri="mock:m2")
	MockEndpoint m2;
	@EndpointInject(uri="mock:end")
	MockEndpoint end;
	
	@Override
	protected RouteBuilder createRouteBuilder() throws Exception{
		return new RouteBuilder(){
			@Override
			public void configure() throws Exception {
				from("dataset:start").loadBalance(new SessionChecker()).to(m1,m2).end().to(end);
			}
		};
	}
	
	@Override
	protected Context createJndiContext() throws Exception{
		SimpleDataSet sds = new SimpleDataSet();
		Map<String, Object> headers = new HashMap<>();
		headers.put("sessionID", 1);
		sds.setDefaultHeaders(headers);
		sds.setSize(2);
		Context context = super.createJndiContext();
		context.bind("start", sds);
		return context;
	}
	
	@Test
	public void testSending() throws Exception{	
		m1.expectedMessageCount(0);
		m2.expectedMessageCount(2);
		end.expectedMessageCount(2);
		template.sendBody("dataset:start", "");
		m1.assertIsSatisfied();
		m2.assertIsSatisfied();
		end.assertIsSatisfied();
	}
}

3. Conclusion

Still there’s more into Camel load balancing. We didn’t cover policies such as Failover, Sticky and Circuit breaker. In fact Camel goes beyond this. You can use load balancing in many more situations such as an HTTP proxy between client and server.

4. Download the Eclipse project

This was an example of different Camel load balancing policies.

Download
You can download the full source code of this example here: Camel load balancer

Hessam Moqaddam

Started with .NET but quickly moved to the Java world. Now works for a company that provides insurance solutions. His main area of interest is core Java and data structures and algorithms.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Senait Moti
Senait Moti
4 years ago

i need java code for weighted round robin load balancing for cloud computing.
i am using cloud analyst tool.

Back to top button