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 Endpoint
: Exposes an endpoint (
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.
- Service Endpoint
: Exposes an endpoint (
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 .
Client (Service A) :
- Le client (Service A) effectue un appel via consul discovery pour trouver Service B.
Service B :
- Le service appelé (Service B) répond à la requête envoyée par Service A.
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.
- 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 :
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
Asynchronous Execution:
It enables tasks to run in the background without blocking the main thread.Non-Blocking API:
You can attach callbacks to process results or handle errors when the computation completes, without waiting.Chaining Tasks:
You can define dependent tasks that execute after the completion of the initial task, forming a chain of computations.Exception Handling:
Provides mechanisms to handle exceptions gracefully during the computation process.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émarrer tous les services :
- Démarrez le Service B (
localhost:8081
). - Démarrez le Service A (
localhost:8082
).
- Démarrez le Service B (
Pour tester circuitbreaker arreter le service ClientA puis run http://localhost:8082/call-a