Tuesday, 31 December 2024

CircuitBreaker resilience patteren

 ✅ Goal: 

Create a /users/{id} endpoint that: 

  • Uses @CircuitBreaker to handle downstream failures 

  • Has a @Retry policy 

  • Is rate-limited using @RateLimiter 

  • Uses fallback method when service is down 

 

1️⃣ pom.xml Dependencies 

<dependencies> 
   <!-- Spring Boot Web --> 
   <dependency> 
       <groupId>org.springframework.boot</groupId> 
       <artifactId>spring-boot-starter-web</artifactId> 
   </dependency> 
 
   <!-- Resilience4j --> 
   <dependency> 
       <groupId>io.github.resilience4j</groupId> 
       <artifactId>resilience4j-spring-boot3</artifactId> 
   </dependency> 
 
   <!-- Spring Boot Actuator (optional for metrics) --> 
   <dependency> 
       <groupId>org.springframework.boot</groupId> 
       <artifactId>spring-boot-starter-actuator</artifactId> 
   </dependency> 
</dependencies> 
 

 

2️⃣ application.yml Configuration 

resilience4j: 
 circuitbreaker: 
   instances: 
     userServiceCB: 
       registerHealthIndicator: true 
       failureRateThreshold: 50 
       slidingWindowSize: 5 
       waitDurationInOpenState: 10s 
       permittedNumberOfCallsInHalfOpenState: 2 
       minimumNumberOfCalls: 3 
 
 retry: 
   instances: 
     userServiceCB: 
       maxAttempts: 3 
       waitDuration: 2s 
 
 ratelimiter: 
   instances: 
     userServiceCB: 
       limitForPeriod: 5 
       limitRefreshPeriod: 1s 
       timeoutDuration: 0 
 

 

3️⃣ Service Class (UserService.java) 

package com.example.demo.service; 
 
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; 
import io.github.resilience4j.retry.annotation.Retry; 
import io.github.resilience4j.ratelimiter.annotation.RateLimiter; 
import org.springframework.stereotype.Service; 
 
@Service 
public class UserService { 
 
   @CircuitBreaker(name = "userServiceCB", fallbackMethod = "getUserFallback") 
   @Retry(name = "userServiceCB", fallbackMethod = "getUserFallback") 
   @RateLimiter(name = "userServiceCB", fallbackMethod = "getUserFallback") 
   public String getUserById(Long id) { 
       // Simulate failure 
       if (id % 2 == 0) { 
           throw new RuntimeException("User service is temporarily down."); 
       } 
       return "User with ID: " + id; 
   } 
 
   public String getUserFallback(Long id, Throwable t) { 
       return "Fallback: User data is not available for ID: " + id; 
   } 
} 
 

 

4️⃣ REST Controller (UserController.java) 

package com.example.demo.controller; 
 
import com.example.demo.service.UserService; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.web.bind.annotation.*; 
 
@RestController 
@RequestMapping("/users") 
public class UserController { 
 
   @Autowired 
   private UserService userService; 
 
   @GetMapping("/{id}") 
   public String getUser(@PathVariable Long id) { 
       return userService.getUserById(id); 
   } 
} 
 

 

🔍 Test Results 

Scenario 

Behavior 

/users/1 

Returns actual user (if no failure) 

/users/2 (fails) 

Triggers retry → circuit breaker opens 

While open (10s) 

Immediate fallback 

After 10s 

Circuit half-open → tests 2 calls 

Rate limiter exceeded 

Fallback due to throttling 

 

✅ Summary 

Feature 

Applied via 

Circuit Breaker 

@CircuitBreaker(name = ..., fallbackMethod = ...) 

Retry 

@Retry 

Rate Limiter 

@RateLimiter 

Fallback 

Java method: public String fallback(...) 

Configurable 

Yes, via application.yml 

 

 

Explain   resilience4j.circuitbreaker 

resilience4j: 
 circuitbreaker: 
   instances: 
     userServiceCB: 
       registerHealthIndicator: true 
       failureRateThreshold: 50 
       slidingWindowSize: 5 
       waitDurationInOpenState: 10s 
       permittedNumberOfCallsInHalfOpenState: 2 
       minimumNumberOfCalls: 3 
 

🔍 Explanation: 

Property 

Meaning 

registerHealthIndicator: true 

Registers this circuit breaker in Spring Boot Actuator (/actuator/health, /actuator/circuitbreakers) 

failureRateThreshold: 50 

Circuit opens if 50% or more of the calls fail 

slidingWindowSize: 5 

Last 5 calls are considered to evaluate failure rate 

waitDurationInOpenState: 10s 

After the circuit is opened, it waits 10 seconds before trying again (half-open state) 

permittedNumberOfCallsInHalfOpenState: 2 

When in half-open state, allows 2 trial calls to check if the service has recovered 

minimumNumberOfCalls: 3 

Minimum number of calls needed before failure rate is calculated (helps avoid false triggers) 

 

🔁 resilience4j.retry 

retry: 
   instances: 
     userServiceCB: 
       maxAttempts: 3 
       waitDuration: 2s 
 

🔍 Explanation: 

Property 

Meaning 

maxAttempts: 3 

Retry the method up to 3 times (1 initial + 2 retries) before failing 

waitDuration: 2s 

Wait 2 seconds between retry attempts 

📌 Tip: If all retry attempts fail, then the fallback method is triggered (if defined). 

 

resilience4j.ratelimiter 

ratelimiter: 
   instances: 
     userServiceCB: 
       limitForPeriod: 5 
       limitRefreshPeriod: 1s 
       timeoutDuration: 0 
 

🔍 Explanation: 

Property 

Meaning 

limitForPeriod: 5 

Allows maximum 5 requests per refresh period 

limitRefreshPeriod: 1s 

Resets the limit every 1 second (so 5 requests/sec) 

timeoutDuration: 0 

If the limit is exceeded, the call fails immediately (no wait or queue) 

 

🧠 Example Flow 

Let's say your user endpoint is: 

GET /users/2 
 

  • You call it 5 times per second ✅ (passes) 

  • The 6th call in the same second ❌ (rate-limited) 

  • If half of those calls fail → circuit opens 

  • Circuit stays open for 10 seconds ⏳ 

  • After 10s, allows 2 test calls in half-open 

  • If test calls pass → circuit closes again ✅ 

 

 

Why use Circuit Breaker if failure is failure? 

When a downstream service or dependency is failing, just retrying or flooding it with requests can make things worse. 

Circuit Breaker helps by: 

  1. Stopping repeated calls to a failing service 
    Instead of hammering the broken service with requests (which wastes resources and may crash it further), the circuit breaker “opens” and short-circuits calls immediately, returning a fallback response. 

  1. Saving system resources and improving responsiveness 
    The calling service quickly returns a fallback (or error) instead of waiting on slow or failed calls. This improves overall system performance and user experience. 

  1. Preventing cascading failures 
    If one microservice fails and calls keep piling up, it may cause other dependent services to fail too. Circuit Breaker isolates the failure to just one part of the system. 

  1. Allows time for recovery 
    When the circuit is open, no calls are sent to the failing service for a configured time (e.g., 10 seconds). After that, a few test calls are allowed (half-open state) to see if the service has recovered. 

  1. Graceful degradation 
    Instead of total failure, you can return fallback data or cached results to keep the system partially functional. 

 

Imagine without circuit breaker 

  • User requests → downstream service is down → long timeouts → user waits 

  • Calls keep retrying → downstream service is overwhelmed → more failures 

  • Entire system slows down or crashes (cascading failure) 

 

Imagine with circuit breaker 

  • User requests → service fails 50% of time → circuit opens 

  • Calls instantly return fallback (cached/alternative) response → fast and stable 

  • After 10 seconds, tries a few requests → service healthy → circuit closes 

 

Summary table 

Without Circuit Breaker 

With Circuit Breaker 

Slow failures and timeouts 

Fast failure response (fallback) 

Downstream service overwhelmed 

Downstream service protected 

Risk of cascading failures 

Failures isolated and contained 

Poor user experience (long waits) 

Better user experience (graceful degradation) 

 

No comments:

Post a Comment