Client-side load balancing using Netflix Ribbon

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. The multiple instances are only for resilience and load-sharing, but the client needs to pick only one instance of the service. So, Spring Cloud Netflix Ribbon comes into the picture and provides several algorithms for client-side load-balancing. Spring also provides a smart RestTemplate.

Spring's RestTemplate is a smart client to call microservices registered on the Eureka server, and it automatically integrates two Netflix utilities, such as the Eureka Service Discovery and Ribbon client-side load balancer. Eureka returns the URL of all available instances. Ribbon determines the best available service to use. Just inject the load-balanced RestTemplate by using the @LoadBalanced annotation. Service Discovery is automatic lookup by using logical service-name of registered microservice.

Project setup

spring init -d=cloud-ribbon,cloud-eureka,web,thymeleaf -n=ribbon-client ribbon-client
Using service at https://start.spring.io
Project extracted to '/home/admatic/ribbon-client'

Ribbon is a client-side load balancer that gives you a lot of control over the behavior of HTTP and TCP clients. And RestTemplate can be automatically configured to use Ribbon. To create a load-balanced RestTemplate, create an @Bean RestTemplate and use the @LoadBalanced qualifier:

cd ribbon-client/

cat << EOF > src/main/java/com/example/ribbonclient/RibbonClientApplication.java
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();
    }
}
EOF

Now let's create a service by using this RestTemplate and call the service registered with Eureka:

mkdir -p src/main/java/com/example/ribbonclient/service

cat << EOF > src/main/java/com/example/ribbonclient/service/HelloServiceClient.java
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);
    }
}
EOF

As you can see, here we have autowired the load-balanced RestTemplate to call the services. The RestTemplate is nothing but a high-level implement of the HTTP Client and exposed several methods to call services. But these methods need a URI, and the URI needs to use a virtual host name that is service name, not a host name.

Create a Web Controller

mkdir -p src/main/java/com/example/ribbonclient/controller

cat << EOF > src/main/java/com/example/ribbonclient/controller/HelloWebController.java
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 HelloWebController {

    @Autowired
    HelloServiceClient helloServiceClient;

    @GetMapping("/say-hello")
    String sayHello(ModelMap model) {
        model.put("message", helloServiceClient.sayHello());
        return "hello";
    }
}
EOF

This web controller has one request handler method, sayHello(), and it populates the model object with the message returned by the HelloServiceClient, and fetched data from our REST service. Here, dependency for starters, such as spring-boot-starter-web and spring-boot-starter-thymeleaf, is used to present a view.

mkdir -p src/main/resources/templates/

echo '<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Say Hello Page | admatic.in</title>
    </head>
    <body>
        <h2 th:text="${message}"/>
    </body>
</html>' | tee src/main/resources/templates/hello.html

This is the thymeleaf view file, which renders the view and value of the message return from the controller.

Let's see the application configuration class application.yml:

cat << EOF > src/main/resources/application.yml
spring:
  application:
    name: spring-cloud-ribbon-client

server:
  port: 8282

eureka:
  client:
    service-url:
      default-zone: http://localhost:8761/eureka
  instance:
    prefer-ip-address: true
EOF

Run the Ribbon Client

mvn spring-boot:run

Open the browser with the http://localhost:8761/ URL:

curl http://localhost:8282/say-hello
<!DOCTYPE html>
<html>
    <head>
        <title>Say Hello Page | admatic.in</title>
    </head>
    <body>
        <h2>Hello from Admatic from EurekaClient!</h2>
    </body>
</html>

As you can see, RestTemplate called the SPRING-CLOUD-EUREKA-CLIENT service registered with Eureka to fetch the Hello from Admatic from EurekaClient! string.

results matching ""

    No results matching ""