How to Load Balance RESTful Web Services Using CXF and Apache Camel
This article shows you two approaches to load balance JAXRS web services without an Apache or a hardware load balancer.
Join the DZone community and get the full member experience.
Join For FreeIn this article I am going to show you two approaches to load balance JAXRS web services without an Apache or a hardware load balancer. The full code is available on GitHub. CXF provides clustering strategies which can be used for configuring a load balancer which has already been discussed here. However, load balancing the JAXRS requires a little more effort, which I have described in this article.
Below are the two approaches to load balance JAXRS services using CXF and Apache Camel.
1. Using CXF Clustering With Apache Camel
In this approach, we leverage the CXF clustering and Camel to load balance the JAXRS web service. Following are the components used for achieving the load balancing:
Proxy JAXRS Service
<cxf:rsServer id="LBRSServer" address="/rest"
serviceClass="com.milan.cxf.jaxrs.PersonLBService">
<cxf:providers>
<beans:bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
<beans:bean
class="com.milan.cxf.jaxrs.exception.ApplicationExceptionMapper" />
<beans:bean class="com.milan.cxf.jaxrs.exception.GenericExceptionMapper" />
</cxf:providers>
</cxf:rsServer>
This JAXRS service serves as the proxy for actual JAXRS service implementation which is hosted on multiple servers.
JAXRS Client
<cxf:rsClient id="personRSServiceClient" address="/xyz"
serviceClass="com.milan.cxf.jaxrs.client.PersonRSServiceClient"
inheritHeaders="true">
<cxf:features>
<clustering:loadDistributor>
<clustering:strategy>
<beans:ref bean="sequentialStrategy" />
</clustering:strategy>
</clustering:loadDistributor>
</cxf:features>
<cxf:headers>
<beans:entry key="Accept" value="text/xml" />
</cxf:headers>
</cxf:rsClient>
This is a simple JAXRS client using CXF clustering feature to load balance the two servers hosting the actual JAXRS service. This can be used with Sequential or RandomStrategy to schedule the calling pattern of the two servers.Here I have used sequentialStrategy for load balancing as detailed below:
<util:list id="addressList" value-type="java.lang.String">
<beans:value>${server1}</beans:value>
<beans:value>${server2}</beans:value>
</util:list>
<beans:bean id="sequentialStrategy"
class="org.apache.cxf.clustering.SequentialStrategy">
<beans:property name="alternateAddresses">
<beans:ref bean="addressList" />
</beans:property>
</beans:bean>
The number of servers in the addressList can be anything. In the above code, this server list is coming from config.properties file as shown below:
server1 = http://localhost:8080/CXFRS/services/rest
server2 = http://localhost:8081/CXFRS/services/rest
Camel Route
public class CXFLBRoute extends RouteBuilder{
@Override
public void configure() throws Exception
{
from("cxfrs:bean:LBRSServer").routeId("CXFLBRoute").autoStartup(true).log("${body}").to("cxfrs:bean:personRSServiceClient");
}
}
This is a simple camel route which takes the request from the proxy JAXRS service and passes the message to JAXRS client which calls either of the hosted JAXRS service in the addressList based on the scheduling strategy.
2.Using Apache Camel Load Balancer Pattern
Proxy JAXRS service
<cxf:rsServer id="LBRSServer" address="/rest"
serviceClass="com.milan.cxf.jaxrs.PersonLBService">
<cxf:providers>
<beans:bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
<beans:bean
class="com.milan.cxf.jaxrs.exception.ApplicationExceptionMapper" />
<beans:bean class="com.milan.cxf.jaxrs.exception.GenericExceptionMapper" />
</cxf:providers>
</cxf:rsServer>
This JAXRS service serves as the proxy server for actual JAXRS service which is hosted on multiple servers. This is same as in Approach 1.
Camel Route Using Load Balancer Pattern
public class CamelLoadBalancerRoute extends RouteBuilder{
@Override
public void configure() throws Exception
{
from("cxfrs:bean:LBRSServer").routeId("CamelLBRoute").autoStartup(true).log("${body}").process(new Processor(){
public void process(Exchange exchange)
{
Map<String,Object> headerMap = exchange.getIn().getHeaders();
String camelHttpPath = headerMap.get("CamelHttpPath").toString();
Message message = exchange.getOut();
message.setHeader(Exchange.HTTP_PATH, camelHttpPath);
}
}).convertBodyTo(InputStream.class)
.loadBalance().roundRobin().to("{{server1}}","{{server2}}");
}
}
This is a simple camel route which takes the request from the proxy JAXRS service, converts the body to InputStream and passes the message to one of the servers in the load balancer list. The server list is being populated into Camel PropertiesComponent in the applicationContext.xml as detailed below:
<beans:bean id="properties"
class="org.apache.camel.component.properties.PropertiesComponent">
<beans:property name="location"
value="classpath:/config/config.properties" />
</beans:bean>
Using any of the above two approaches gives us a simple REST URL which we can hit and get a response directly hiding the load balancer implementation completely. Both the approaches can be used for Failover and Circuit Breaker configurations as well.
The code comprises of two Gradle projects:
1. CXLB: This is a load balancer application.This needs to be deployed on one of the nodes in our cluster. Update the con/config.xml file with the JAXRS server list where CXFRS is deployed to make it function correctly.
2. CXFRS: JAXRS web service code. This needs to be deployed on as many nodes as we want in our cluster.
We just need to do Gradle clean build the two projects and deploy the wars on different servers.
Try load-balancing JAXRS web services using this example!
Opinions expressed by DZone contributors are their own.
Comments