Resilience4J for fault tolerance,

<

In a Spring Cloud application utilizing consul for service discovery and Resilience4J for fault tolerance, various components play distinct roles to enhance the architecture's resilience, scalability, and maintainability. Here’s a breakdown of each element and its role in the project:

Consul

  • Role : Acts as a service registry.
  • Function :
    • Service Discovery : It maintains a registry of all available microservices, allowing other services to discover and communicate with them without hardcoding URLs.
    • Registration : Microservices register themselves with the Consul upon startup, providing their metadata (like service name, URL, and health status).
    • Health Monitoring : The server periodically checks the health of registered services and deregisters them if they become unresponsive or fail.

Service A

  • Role : A microservice that can handle requests and respond with data.
  • Function :
    • Service Endpoint : Exposes an endpoint (/serviceA ) that can be accessed by other services (like Service B).
    • Self-Discovery : Registers with the Consul to be discoverable by Service B and other clients.

Service B

  • Role : A microservice that interacts with Service A.
  • Function :
    • Service Endpoint : Exposes an endpoint (/serviceB ) which calls Service A and returns its response.
    • Dependency Management : Utilizes a RestTemplate to make HTTP requests to Service A, allowing it to retrieve and display data from Service A.
    • Circuit Breaker : Uses Resilience4J’s Circuit Breaker to handle potential failures in calls to Service A, preventing cascading failures if Service A is down.

Resilience4J

  • Role : A library for fault tolerance.
  • Function :
    • Circuit Breaker : Prevents cascading failures by stopping requests to a failing service after a certain number of failures. When the circuit breaker opens, subsequent requests to the failing service are quickly failed until it recovers.
    • Rate Limiter : (If implemented) Controls the rate of requests sent to a service, preventing overload.
    • Bulkhead : (If implemented) Isolates different parts of the application to ensure that if one part fails, the others can still operate.
    • Retry Mechanism : (If implemented) Automatically retries failed requests to recover from transient errors.

RestTemplate

  • Role : A synchronous client to make HTTP requests.
  • Function :
    • Service Communication : Used by Service B and Service C to call other microservices. It abstracts the complexities of making HTTP calls, handling serialization/deserialization of requests and responses.
    • Load Balancing : With the @LoadBalanced annotation, it allows calls to other services by their registered names (e.g., http://service-a/serviceA ), automatically resolving their actual IP addresses through the Consul .
graph TD A[ClientA] -->|Call via Consul| B[Service B] A -->|Invoke /invoke| C[Service A Client RestTemplate] subgraph Resilience4J Mechanisms direction TB D[Retry] --> E[Rate Limiter] E --> F[Time Limiter] F --> G[Circuit Breaker] end C -->|Handles Mechanisms| D C -->|Call Service B with Circuit Breaker| B B -->|Return Response| A classDef resilience fill:#f9f,stroke:#333,stroke-width:2px; class D,E,F,G resilience;
  1. Client (Service A) :

    • Le client (Service A) effectue un appel via consul discovery pour trouver Service B.
  2. Service B :

    • Le service appelé (Service B) répond à la requête envoyée par Service A.
  3. Service A Client (RestTemplate) :

    • Cette partie du Service A est responsable de l'appel HTTP à Service B. Elle est protégée par les mécanismes de résilience suivants :
      • Retry : En cas d'échec, l'appel est réessayé plusieurs fois.
      • Rate Limiter : Limite le nombre d'appels à Service B dans une période donnée.
      • Time Limiter : Si le service prend trop de temps pour répondre, l'appel est interrompu.
      • Circuit Breaker : Si Service B échoue plusieurs fois, le circuit est ouvert pour éviter d'inonder Service B de requêtes.
  4. Mécanismes de Résilience :

    • Ces mécanismes assurent la résilience de l'application face aux pannes ou aux délais de réponse longs.

Objectif du Projet

  • Eureka or Consul pour la découverte de services.
  • Resilience4J pour gérer la résilience des appels interservices à l'aide des mécanismes suivants :
    • Circuit Breaker : Protège contre les appels à un service défaillant.
    • Retry : Réessaie automatiquement un appel en cas d'échec.
    • Rate Limiter : Limite le nombre d'appels à un service par unité de temps.
    • Time Limiter : Ajoute une limite de temps pour les appels aux services externes.

atructure de l'Application

  • Consul or Consul : Service de découverte des microservices.
  • Service A : Service client qui appelle un autre service (Service B).
  • Service B : Service appelé (par Service A).

Créer Client A

Définir un projet Spring Boot

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-consul-discovery</artifactId>
		</dependency>
	
		  <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
Configuration du application.properties :

# Spring application name
spring.application.name=clienta

# Spring Cloud Consul configuration
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.enabled=true


# Management endpoints exposure
management.endpoints.web.exposure.include=health,info,refresh

# Server port configuration
server.port=8080

Controller

package com.example.clienta;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ClientAController {
    
    @GetMapping("/greet")
    public String greet() {
        return "Hello from Client A!";
    }
}
ClientAApplication

@SpringBootApplication
public class ClientAApplication {

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

}

Lance Consul

Créer le Service B (Service Externe)

Créez un projet Spring Boot .

	<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-consul-discovery</artifactId>
		</dependency>
  <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-spring-boot2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        
        
        
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

Configurer application.properties

# Spring Application Configuration
server.port=8083
spring.application.name=client-b

# Spring Cloud Consul Configuration
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.prefer-ip-address=true

# Management Endpoints
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.health.circuitbreakers.enabled=true

# Resilience4j Circuit Breaker Configuration for serviceB
resilience4j.circuitbreaker.instances.serviceA.baseConfig=default


# Resilience4j Retry Configuration for serviceB
resilience4j.retry.instances.serviceA.maxAttempts=3
resilience4j.retry.instances.serviceA.waitDuration=2s

# Resilience4j Rate Limiter Configuration for serviceB
# Limit the number of calls to 3 per 5 seconds
resilience4j.ratelimiter.instances.serviceA.limitForPeriod=3
resilience4j.ratelimiter.instances.serviceA.limitRefreshPeriod=10s

# Resilience4j Time Limiter Configuration for serviceB
resilience4j.timelimiter.instances.serviceA.timeoutDuration=2s
# Resilience4j Rate Limiter Configuration for serviceB


Créer un contrôleur pour le Service B : Exposez une API REST pour simuler un service.
package com.example.clientb;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import io.github.resilience4j.retry.annotation.Retry;
import io.github.resilience4j.timelimiter.annotation.TimeLimiter;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.stereotype.Controller;
import java.util.concurrent.CompletableFuture;
import java.util.List;

@RestController
public class ClientBController {

    @Autowired
    private DiscoveryClient discoveryClient;

    private final RestTemplate restTemplate = new RestTemplate();


  

    @GetMapping("/call-a")
    @CircuitBreaker(name = "serviceA", fallbackMethod = "callifNoReponse")
    @Retry(name = "serviceA", fallbackMethod = "callifNoReponse")
    @RateLimiter(name = "serviceA",fallbackMethod = "callifNoReponse")
    @TimeLimiter(name = "serviceA", fallbackMethod = "callifNoReponse")
    public CompletableFuture callClientA() {
        return CompletableFuture.supplyAsync(() -> {
        List<ServiceInstance> instances = discoveryClient.getInstances("clienta");
        if (instances != null && !instances.isEmpty()) {
            String clientAUrl = instances.get(0).getUri().toString() + "/greet";
            return restTemplate.getForObject(clientAUrl, String.class);
        }
        return "Client A not available";
        });
    }

    
 // Fallback method with Throwable parameter
    public CompletableFuture<String> callifNoReponse(Throwable throwable) {
        // Handle the fallback logic here (e.g., return a default message)
        return CompletableFuture.completedFuture("Fallback response: " + throwable.getMessage());
    }
}

ClientBApplication


package com.example.clientb;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;

@SpringBootApplication
public class ClientBApplication {

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

	
	 @Bean
	    public RestTemplate restTemplate() {
	        return new RestTemplate();
	    }

	    @Bean
	    public CircuitBreakerRegistry circuitBreakerRegistry() {
	        return CircuitBreakerRegistry.ofDefaults();
	    }
	    
}

CompletableFuture

  1. Asynchronous Execution:
    It enables tasks to run in the background without blocking the main thread.

  2. Non-Blocking API:
    You can attach callbacks to process results or handle errors when the computation completes, without waiting.

  3. Chaining Tasks:
    You can define dependent tasks that execute after the completion of the initial task, forming a chain of computations.

  4. Exception Handling:
    Provides mechanisms to handle exceptions gracefully during the computation process.

  5. Combination of Futures:
    Allows combining multiple asynchronous tasks to execute either sequentially or concurrently.

  • @CircuitBreaker : Si Service B échoue, cette annotation active le fallback.
  • @Retry : Réessaie l'appel en cas d'échec (max 3 tentatives).
  • @RateLimiter : Limite le nombre d'appels (5 appels toutes les 10 secondes).
  • @TimeLimiter : Limite la durée de l'appel à 2 secondes.
Démarrez le Service A. Il sera accessible sur http://localhost:8082/call-a et appellera le Service B.
  1. Démarrer tous les services :

    • Démarrez le Service B (localhost:8081).
    • Démarrez le Service A (localhost:8082).
pour tester RateLimeter :Run http://localhost:8082/call-a 5 fois
Pour tester circuitbreaker arreter le service ClientA puis run http://localhost:8082/call-a