Service Discovery and Client-Side Load Balancing With Eureka and Ribbon
Want to learn how to build an application with service discovery and client-side load balancing? Check out this post to learn more about using Eureka and Ribbon.
Join the DZone community and get the full member experience.
Join For FreeToday, we are going to build an application with load balancing and service discovery. Traditionally, a load balancer is a separate application running that has all server nodes, as well. We will build an application where the client does not need to remember the IPs of a server, and we can dynamically add and remove nodes.
Let's start with app1 and app 2, which are two applications or servers with REST endpoints, such as /process
. We are going to discover and load balance it. App1 and app2 will register themselves into the Eureka server with the name app-producer whenever they get started.
The app consumer is the consumer application, called app-producer, which also register themselves to the Eureka service registry as app-consumer.eureka
.
Step-By-Step Code
A request comes to the app-consumer (localhost:7070/process), and the app consumer ask the Eureka server to show the available instances. Then, Eureka has two instances of the app-producer registered, so the client load balance is set in a round-robin manner.
Eureka 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>zuul</groupId>
<artifactId>zuul</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringBootHelloWorld</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.1.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>3.10.3</version>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-client</artifactId>
<version>3.10.3</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Eureka app
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableEurekaServer
public class Eureka
{
public static void main( String[] args )
{
SpringApplication.run(Eureka.class, args);
}
}
App1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.app1</groupId>
<artifactId>app1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>app1</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
App1 application
package com.app1.app1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class App1Application {
public static void main(String[] args) {
SpringApplication.run(App1Application.class, args);
}
}
App1 REST endpoint
package com.app1.app1;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class AppController {
@RequestMapping(value="/process", method = RequestMethod.GET)
public @ResponseBody String showLoginPage(ModelMap model){
return "Processing aap1";
}
}
App1 properties
server.port=8091
eureka.client.serviceUrl.defaultZone=http://localhost:8080/eureka
spring.application.name=app-producer
eureka.instance.instanceId=${spring.application.name}:${random.int}
For this tutorial, I am not adding code for app 2. Just create the same application as app1. The only change will be its REST endpoint, which will give us "Processing app2."
Now, let's start with app consumer:
application code
package com.app2.app2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class AppConsumer {
public static void main(String[] args) {
SpringApplication.run(AppConsumer.class, args);
}
@Bean
public Client client()
{
return new Client();
}
}
REST endpoint with client-side balancing
package com.app2.app2;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestClientException;
@Controller
public class AppController {
@Autowired
private Client client;
@RequestMapping(value="/process", method = RequestMethod.GET)
public @ResponseBody String showLoginPage(ModelMap model) throws RestClientException, IOException{
return client.getEmployee("/process");
}
}
package com.app2.app2;
import java.io.IOException;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
public class Client {
@Autowired
private LoadBalancerClient loadBalancer;
public String getEmployee(String url) throws RestClientException, IOException {
ServiceInstance serviceInstance=loadBalancer.choose("app-producer");
System.out.println(serviceInstance.getUri());
String baseUrl=serviceInstance.getUri().toString()+url;
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = null;
try {
response = restTemplate.exchange(baseUrl, HttpMethod.GET, getHeaders(), String.class);
} catch (Exception ex) {
System.out.println(ex);
}
return response.getBody();
}
private static HttpEntity<?> getHeaders() throws IOException {
HttpHeaders headers = new HttpHeaders();
headers.set("Accept", MediaType.TEXT_PLAIN_VALUE);
return new HttpEntity<>(headers);
}
}
application.properties
server.port=7070
eureka.client.serviceUrl.defaultZone=http://localhost:8080/eureka
spring.application.name=app-consumer
eureka.instance.instanceId=${spring.application.name}:${random.int}
Lastly, you will need to deploy all Spring Boot applications and hit the localhost:7070/process.
The output will be in a round-robin manner:
Processing aap1
Processing aap2
Processing aap1
Opinions expressed by DZone contributors are their own.
Comments