Spring Cloud Netflix provides an easy Cloud integration for Spring Boot apps through autoconfiguration and binding to the Spring Environment.
By adding few simple annotations you can quickly leverage and configure the common patterns inside your application and build large distributed systems with Netflix components. In this tutorial we will cover Service Discovery (Eureka) and Client Side Load Balancing (Ribbon).
Service Discovery with Netflix Eureka
By using Netflix Eureka, you can register multiple services in a Cloud environment. These register instances of the service act as a server and replicate its status to the connected peer clients. Netflix Eureka manages the requests to the instances of a service using a load balancing algorithm. The client retrieves a list of all connected instances of a service registry and distributes the loads to these instances using a load-balancing algorithm, such as Client Side Load Balancing (Ribbon) does.
Let’s start by implementing the server-side service registry (Eureka Server) first. Open the Terminal (or use Spring Initializr) and create a project with the following dependencies:
spring init -n eureka-server -dcloud-eureka-server
The following dependencies will be added:
<properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Now enable the Eureka server by using the @EnableEurekaServer annotation in a main Spring Boot application class that is also annotated with the @SpringBootApplication annotation.
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
That’s all you need to configure an Eureka Server. In terms of configuration, define in the application.yml the following properties:
eureka: client: registerWithEureka: false fetchRegistry: false server: port: 8761
- registerWithEureka: If we make this property as true then while the server starts the inbuilt client will try to register itself with the Eureka server.
- fetchRegistry: The inbuilt client will try to fetch the Eureka registry if we configure this property as true.
Replica node URL: http://localhost:8761/eureka/ Changing status to UP Started Eureka Server Tomcat started on port(s): 8761 (http) with context path '' Updating port to 8761
we can point our browser to http://localhost:8761 to view the Eureka dashboard as follows:
Implementing Service Discovery—Eureka Clients
Now that we have bootstrapped Eureka, we will add an Eureka Client. To do that, create a project with the following dependencies:
spring init -n eureka-client -dcloud-eureka,web
The following dependencies will be added:
<properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <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> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Registering a client with Eureka means that a client provides its own meta-information (hostname, port, health indicator URL, and homepage). Each instance of a service sends heartbeat messages to the Eureka server; if Eureka doesn’t receive the heartbeat over a configurable timetable, the instance is removed from the registry.
Now lets enable the Application class to be an Eureka Client, by adding the annotation @EnableEurekaClient:
package com.example.eurekaclient; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class EurekaClientApplication { public static void main(String[] args) { SpringApplication.run(EurekaClientApplication.class, args); } }
We will then create a REST service to be registered with the Client:
package com.example.eurekaclient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/hello") public String sayHello() { return "Hello World from EurekaClient!"; } }
Now let’s add the application.yml configuration file for this client application as follows:
spring: application: name: spring-cloud-eureka-client server: port: 8180 eureka: client: service-url: default-zone: ${EUREKA_URI:http://localhost:8761/eureka} instance: prefer-ip-address: true
Start the application. You should see on your Eureka Server Console that the service was registered:
2019-09-02 19:54:43.424 INFO 32676 --- [nio-8761-exec-2] c.n.e.registry.AbstractInstanceRegistry : Registered instance SPRING-CLOUD-EUREKA-CLIENT/fedora.station:spring-cloud-eureka-client:8180 with status UP (replication=false) 2019-09-02 19:54:44.063 INFO 32676 --- [nio-8761-exec-3] c.n.e.registry.AbstractInstanceRegistry : Registered instance SPRING-CLOUD-EUREKA-CLIENT/fedora.station:spring-cloud-eureka-client:8180 with status UP (replication=true) 2019-09-02 19:54:50.596 INFO 32676 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry : Running the evict task with compensationTime 0ms
And conversely on the Client console:
[nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_SPRING-CLOUD-EUREKA-CLIENT/fedora.station:spring-cloud-eureka-client:8180 - registration status: 204 2019-09-02 19:55:13.258 INFO 576 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient : Disable delta property : false
If you browse to http://localhost:8761 to view the Eureka dashboard you should see the Client that has been discovered:
Client-side load balancing using Netflix Ribbon
Client-side load balancing is used to balance requests to the microservices. Implementing client-side load balancing provides a way to distribute the load across multiple instances. The Discovery server returns the location of these multiple instances. These instances are only for resilience and load-sharing, but the client needs to pick only one instance of the service. Ribbon is a client-side load balancer that gives you a lot of control over the behavior of HTTP and TCP clients.
We can bootstrap a Ribbon Cloud application as follows:
spring init -n client-ribbon -dthymeleaf,web,cloud-ribbon,cloud-eureka
The following dependencies will be added:
<properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Our application will include the @EnableEurekaClient but also the @LoadBalanced, which is placed on a method that returns an instance of a RestTemplate:
package com.example.ribbonclient; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableEurekaClient public class RibbonClientApplication { public static void main(String[] args) { SpringApplication.run(RibbonClientApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
The RestTemplate is autowired in the following Service class, which binds the request to the Eureka Client we have just registered:
package com.example.ribbonclient.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @Service public class HelloServiceClient { @Autowired @LoadBalanced RestTemplate restTemplate; public String sayHello() { return restTemplate.getForObject("http://SPRING-CLOUD-EUREKA-CLIENT/hello", String.class); } }
Finally, we include a simple Controller which makes the Ribbon client balancer available at the “/hello” mapping:
package com.example.ribbonclient.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import com.example.ribbonclient.service.HelloServiceClient; @Controller public class HelloController { @Autowired HelloServiceClient helloServiceClient; @GetMapping("/hello") String sayHello(ModelMap model) { model.put("message", helloServiceClient.sayHello()); return "hello"; } }
We will also include a static hello.html which shows the message text as thymeleaf tag:
<html xmlns:th="http://www.thymeleaf.org"> <body> <h2 th:text="${message}"></h2> </body> </html>
The last piece of information is the application.yml file for the Ribbon Client:
spring: application: name: spring-cloud-ribbon-client server: port: 8181 eureka: client: service-url: default-zone: ${EUREKA_URI:http://localhost:8761/eureka} instance: prefer-ip-address: true
Now run this client application and open the browser at http://localhost:8761/ :
As you can see, on the Eureka Dashboard, there are two services registered as SPRING-CLOUD-EUREKA-CLIENT , and SPRING-CLOUD-RIBBON-CLIENT.
After running this example, we’ll open our browser and go to http://localhost:8181/hello and it should display something like the following:
Congrats! You just managed to run your an Eureka Client-Server application with a LoadBalancer Ribbon Client. In the next tutorial, we will show how to use a Feign Client, to simplify the creation of Netflix Clients: Getting Started with NetFlix Feign Client
Source code for this article is available at: https://github.com/fmarchioni/masterspringboot/tree/master/cloud/eureka