devstory

Comprendre l'équilibrage de charge dans Spring Cloud avec Ribbon et exemple

  1. Qu'est-ce que Load Balancer ?
  2. Netflix Ribbon
  3. L'objectif de la leçon
  4. Créer un projet Spring Boot
  5. Configurer Ribbon
  6. Controller
  7. Exécuter l'application

1. Qu'est-ce que Load Balancer ?

Imaginez que vous ayez un système distribué composé de nombreux services (applications) fonctionnant sur différents ordinateurs. Pourtant, lorsque le nombre d'utilisateurs est considérable, un service (application) est généralement créé plusieurs répliques. Chaque réplique exécute sur un ordinateur différent. À ce moment, "Load Balancer" (L'équilibreur de charge) apparaît, il permet de répartir le trafic entrant (incomming traffic) de manière égale entre les serveurs.
Server side Load Balancer
Traditionnellement, des équilibreurs de charge (Load Balancer) sont des composants situés du côté serveur (Server Side). Lorsque les demandes proviennent du Client, elles iront à l'équilibreur de charge, et l'équilibreur de charge désignera un Server pour la demande. L'algorithme le plus simple utilisé par l'équilibreur de charge est la désignation aléatoire. Dans la plupart des cas, les équilibreurs de charge sont des logiciels intégrés pour contrôler l'équilibrage de charge (load balancing).
L'équilibreur de charge à côté du Client (Client-Side Load Balancer) :
Lorsque l'équilibreur de charge est trouvé à côté du Client (Client side), il va activement décider quel serveur recevra des demandes à la base de certaines critères.
Actuellement, des serveurs sont différents, les critères qui les distiguent sont listées ci-dessous :
  1. La disponibilité (Availability) : Tous les serveurs ne fonctionnnent pas en même temps.
  2. Les performances (Performance): La vitesse des serveurs est différente.
  3. La géographie : Des serveurs se sont placés de différentes positions, par exemple, ils se trouvent à différents pays et ils peuvent être proches ce Client mais se trouvent loin d'autres Client.
L'équilibreur de charge à côté du Client envoie fréquemment des demandes à la même zone (Zone), ou avec une réponse rapide.

2. Netflix Ribbon

Ribbon est une partie de la famille Netflix Open Source Software (Netflix OSS). Elle est la bibliothèque qui fournit l'équilibreur de charge à côté du Client. Parcequ'étant membre de la famille Netflix elle peut automatiquêmnt interagir avec Netflix Service Discovery (Eureka).
Spring Cloud crée des API qui vous aide à utiliser facilement des bibliothèques Ribbon.
OK, nous discuterons ce concept principal lié au Ribbon:
  1. La liste des serveurs.
  2. La liste filtrée des serveurs.
  3. L'équilibreur de charge (Load Balancer)
  4. Ping
-
  • La liste des serveur (List Of Servers) :
Une liste de serveurs peut répondre à un service particulier pour 1 Client. Par exemple, 1 Client a besoin d'informations de la météo. Il y aura une liste de serveurs pouvant fournir cette information. Cette liste comprend les serveurs configurés directement dans l'application Client et les serveurs sont découverts par le Client.
  • La liste filtrée des serveurs (Filtered List of Servers) :
Nous continuons l'exemple ci-dessus. Un Client a besoin des informations météorologiques et une liste de serveurs peut fournir ces informations. Cependant, tous ces serveurs ne fonctionnent pas, ou ces serveurs se trouvent trop éloignés du Client; par conséquent, ils répondent très lentement. Le Client supprimera ces serveurs de la liste et, éventuellement, il aura une liste de serveurs plus appropriée (une liste filtrée).
  • Load Balancer (Ribbon):
Ribbon est un équilibreur de charge. Il est un componant à côté du Client, il décide quel serveur sera appelé (de la liste filtrée des serveurs).
Il y a quelques stratégies (strategy) afin de donner une décision. Mais elle est fréquemment liée sur un "Rule Component" (Le componant de règle) pour donner la décision finale. Par défaut, Spring Cloud Ribbon utilise la stratégie ZoneAwareLoadBalancer (Des serveurs se trouvant de même zone du Client).
Rule Component est un module intélligent. Il crée une décision "Appeler ou ne pas appeler". Par défaut, Spring Cloud applique la règle ZoneAvoidanceRule.
  • Ping:
Ping est la manière que le Client vérifie rapidement le satut actif ou désactif du serveur. Le comportement par défaut du Spring Cloud est délégué Eureka pour vérifier automatiquement ces informations. Pourtant Spring Cloud vous permet de le personnaliser à votre manière.

3. L'objectif de la leçon

Il est à noter que cette leçon est lié directement sur la base de deux leçon précédentes.
  • Leçon (1): Nous avons créé un "Service Registration" (Eureka Server).
  • Leçon (2): Nous avons créé une application "ABC Service" qui est un Discovery Client (Eureka Client).
Dans cetteleçon, nous allons créer une application"XYZ Service", elle est également un Discovery Client (Eureka Client) et elle appelera "ABC Service". Sur l'application "XYZ Service" nous allons uiliser un équilibreur de charge.
Pour être en mesure de tester cette application, nous devons exécuter les trois applications, dont l'application "ABC Service" sera créé 5 répliques (replicas) (simulant qu'il fonctionne sur 5 ordinateurs différents). Lorsque le Client (XYZ Service) appelle le "ABC Service", l'équilibreur de charge décide quelle réplique de "ABC Service" sera appelée.
OK!, C'est un travail intéressant et assez facile.

4. Créer un projet Spring Boot

Sur Eclipse, créez un projet Spring Boot.
Saisissez :
  • Name: SpringCloudLoadBalancerRibbon
  • Group: org.o7planning
  • Artifact: SpringCloudLoadBalancerRibbon
  • Description: Spring Cloud Load Balancer (Ribbon)
  • Package: org.o7planning.ribbon
  • L'application que nous allons créer appelera "ABC Service", mais en effet, elle n'a besoin d'un seul Load Balancer (L'équilibreur de charge), donc nous allons déclarer de l'utilisation de la bibliothèque Ribbon.
  • Cette application devrait également découvrir d'autres services (applications) exécutant dans le système distribué; donc, il nécessite d'utiliser la bibliothèque Discovery, nous allons utiliser Eureka Discovery (Eureka Client).
OK, le projet a été créé.
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>org.o7planning</groupId>
    <artifactId>SpringCloudLoadBalancerRibbon</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>SpringCloudLoadBalancerRibbon</name>
    <description>Spring Cloud Load Balancer (Ribbon)</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.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>
        <spring-cloud.version>Edgware.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</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>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

5. Configurer Ribbon

application.yml
spring:
  application:
    name: XYZ-SERVICE
 
server:
  port: 5555

# -- Configure for Ribbon:
 
ping-server:
  ribbon:
    eureka:
      enabled: false # Disable Default Ping
    listOfServers: localhost:8000,localhost:8001,localhost:8002,,localhost:8003
    ServerListRefreshInterval: 15000
    
# -- Configure Discovery Client (Eureka Client).    
# Configure this application to known "Service Registration".

eureka:
  instance:
    appname: XYZ-SERVICE  # ==> This is an instance of XYZ-SERVICE
  client:    
    fetchRegistry: true
    serviceUrl:
#      defaultZone: http://my-eureka-server.com:9000/eureka
      defaultZone: http://my-eureka-server-us.com:9001/eureka
#      defaultZone: http://my-eureka-server-fr.com:9002/eureka
#      defaultZone: http://my-eureka-server-vn.com:9003/eureka
SpringCloudLoadBalancerRibbonApplication.java
package org.o7planning.ribbon;

import org.o7planning.ribbon.config.RibbonConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

@RibbonClient(name = "ping-a-server", configuration = RibbonConfiguration.class)
@EnableEurekaClient
@SpringBootApplication
public class SpringCloudLoadBalancerRibbonApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringCloudLoadBalancerRibbonApplication.class, args);
    }

}
RibbonConfiguration.java
package org.o7planning.ribbon.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.PingUrl;
import com.netflix.loadbalancer.WeightedResponseTimeRule;

 
public class RibbonConfiguration {
    
    @Autowired
    private IClientConfig ribbonClientConfig;
 
    @Bean
    public IPing ribbonPing(IClientConfig config) {
        return new PingUrl();
    }
 
    @Bean
    public IRule ribbonRule(IClientConfig config) {
        return new WeightedResponseTimeRule();
    }
    
}

6. Controller

Example1Controller.java
package org.o7planning.ribbon.controller;

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.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class Example1Controller {

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private LoadBalancerClient loadBalancer;

    @ResponseBody
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String home() {

        return "<a href='testCallAbcService'>/testCallAbcService</a>";
    }

    @ResponseBody
    @RequestMapping(value = "/testCallAbcService", method = RequestMethod.GET)
    public String showFirstService() {

        String serviceId = "ABC-SERVICE".toLowerCase();

        // (Need!!) eureka.client.fetchRegistry=true
        List<ServiceInstance> instances = this.discoveryClient.getInstances(serviceId);

        if (instances == null || instances.isEmpty()) {
            return "No instances for service: " + serviceId;
        }
        String html = "<h2>Instances for Service Id: " + serviceId + "</h2>";

        for (ServiceInstance serviceInstance : instances) {
            html += "<h3>Instance :" + serviceInstance.getUri() + "</h3>";
        }

        // Create a RestTemplate.
        RestTemplate restTemplate = new RestTemplate();

        html += "<br><h4>Call /hello of service: " + serviceId + "</h4>";

        try {
            // May be throw IllegalStateException (No instances available)
            ServiceInstance serviceInstance = this.loadBalancer.choose(serviceId);

            html += "<br>===> Load Balancer choose: " + serviceInstance.getUri();

            String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/hello";

            html += "<br>Make a Call: " + url;
            html += "<br>";

            String result = restTemplate.getForObject(url, String.class);

            html += "<br>Result: " + result;
        } catch (IllegalStateException e) {
            html += "<br>loadBalancer.choose ERROR: " + e.getMessage();
            e.printStackTrace();
        } catch (Exception e) {
            html += "<br>Other ERROR: " + e.getMessage();
            e.printStackTrace();
        }
        return html;
    }

}

7. Exécuter l'application

Pour tester complètement cette application , vous devez exécuter deux applications de deux leçon précédentes ("Service Registration""ABC Service")
Ensuite, vous pouvez exécuter cette application directement sur Eclipse. Et vous pouvez accéder le lien ci-dessous pour voir le Eureka Monitor (Le moniteur de Eureka).
Test Load Balancer: