Circuit Breaker Pattern with a Practical Example
Created on: Sep 17, 2024
The circuit breaker pattern is a software design pattern that helps prevent applications from repeatedly trying to perform an operation that is likely to fail.
A circuit breaker acts as a proxy for operations that might fail. The proxy should monitor the number of recent failures that have occurred, and use this information to decide whether to allow the operation to proceed, or simply return an exception immediately.
The proxy can be implemented as a state machine with the following states.
- Closed: The request from the application is routed to the operation. The proxy maintains a count of the number of recent failures, and if the call to the operation is unsuccessful the proxy increments this count. If the number of recent failures exceeds a specified threshold within a given time period, the proxy is placed into the Open state. At this point the proxy starts a timeout timer, and when this timer expires the proxy is placed into the Half-Open state. The purpose of the timeout timer is to give the system time to fix the problem that caused the failure before allowing the application to try to perform the operation again.
- Open: The request from the application fails immediately and an exception is returned to the application.
- Half-Open: A limited number of requests from the application are allowed to pass through and invoke the operation. If these requests are successful, it's assumed that the fault that was previously causing the failure has been fixed and the circuit breaker switches to the Closed state (the failure counter is reset). If any request fails, the circuit breaker assumes that the fault is still present so it reverts to the Open state and restarts the timeout timer to give the system a further period of time to recover from the failure.
Enough with the theory, let's understand this with an example. Suppose we have a streaming platform like Netflix or Amazon Prime Video. When a user visits the home page, they can see the recommended movies. In this case, request comes to streaming service which then calls recommendation service. Recommendation service returns recommendation based on the user. Suppose the recommendation service is down. In that case, the user won't see anything on the homepage, leading to a bad experience. This is where the circuit breaker pattern can be used.
Clone my GitHub repo to see the practical example.
git clone https://github.com/keshav-repo/spring-boot-concepts/tree/master/circuitBreak cd circuitBreak
Now let's run both the streaming and recommendation service in different terminal,
# Run recommendation service cd recommendation mvn spring-boot:run # run streaming service cd streaming mvn spring-boot:run
And hit below url
curl --location --request GET 'localhost:8090/api/home'
You can see the recommendations that come from the recommendation service.
[ { "title": "Stranger Things", "type": "Series", "genre": ["Sci-Fi", "Drama"], "rating": 4.8, "thumbnailUrl": "https://cdn.netflix.com/thumbnails/stranger-things.jpg" }, { "title": "The Witcher", "type": "Series", "genre": ["Fantasy", "Action"], "rating": 4.7, "thumbnailUrl": "https://cdn.netflix.com/thumbnails/the-witcher.jpg" }, { "title": "Breaking Bad", "type": "Series", "genre": ["Crime", "Drama"], "rating": 4.9, "thumbnailUrl": "https://cdn.netflix.com/thumbnails/breaking-bad.jpg" } ]
Now, let's stop the recommendation service and hit the same URL again.
[ { "title": "Money Heist", "type": "Series", "genre": [ "Crime", "Thriller" ], "rating": 4.6, "thumbnailUrl": "https://cdn.netflix.com/thumbnails/money-heist.jpg" }, { "title": "The Crown", "type": "Series", "genre": [ "Historical", "Drama" ], "rating": 4.7, "thumbnailUrl": "https://cdn.netflix.com/thumbnails/the-crown.jpg" }, { "title": "The Queen's Gambit", "type": "Series", "genre": [ "Drama", "Sport" ], "rating": 4.9, "thumbnailUrl": "https://cdn.netflix.com/thumbnails/queens-gambit.jpg" } ]
You will see a different response with static data if the recommendation service fails.This happens because circuit breaker is configured which give the fallback response that you can see above.
Let's also check the code. The @CircuitBreaker annotation specifies that if an exception occurs, it will call the fallbackMethod.
@GetMapping("/api/home") @CircuitBreaker(name = "recommendation", fallbackMethod = "fallbackMethod") public ResponseEntity<?> homePage() { String url = "http://localhost:8080/api/recommendations"; try { log.info("calling the recommendation service"); ResponseEntity<List<Recommendation>> responseEntity = restTemplate.exchange( url, HttpMethod.GET, null, new ParameterizedTypeReference<List<Recommendation>>() { } ); List<Recommendation> recommendations = responseEntity.getBody(); return ResponseEntity.ok(recommendations); }catch (HttpClientErrorException e) { throw e; } catch (Exception e) { throw new RuntimeException("Service unavailable"); } } public ResponseEntity<?> fallbackMethod(Exception e) { Recommendation moneyHeist = Recommendation.builder() .title("Money Heist") .type("Series") .genre(List.of("Crime", "Thriller")) .rating(4.6) .thumbnailUrl("https://cdn.netflix.com/thumbnails/money-heist.jpg") .build(); Recommendation queensGambit = Recommendation.builder() .title("The Queen's Gambit") .type("Series") .genre(List.of("Drama", "Sport")) .rating(4.9) .thumbnailUrl("https://cdn.netflix.com/thumbnails/queens-gambit.jpg") .build(); Recommendation theCrown = Recommendation.builder() .title("The Crown") .type("Series") .genre(List.of("Historical", "Drama")) .rating(4.7) .thumbnailUrl("https://cdn.netflix.com/thumbnails/the-crown.jpg") .build(); return ResponseEntity.ok( Arrays.asList( moneyHeist, theCrown, queensGambit ) ); }
Below is spring boot properties file
resilience4j.circuitbreaker: configs: default: registerHealthIndicator: true slidingWindowSize: 10 minimumNumberOfCalls: 5 permittedNumberOfCallsInHalfOpenState: 3 automaticTransitionFromOpenToHalfOpenEnabled: true waitDurationInOpenState: 5s failureRateThreshold: 50 eventConsumerBufferSize: 10
In the provided configuration, the slidingWindowSize
of 10 means the Circuit Breaker will consider the most recent 10 calls to evaluate success or failure rates. If at least 5 calls are made and the failure rate exceeds 50%, the Circuit Breaker will transition to the Open state, where no requests will be forwarded to the external service, and only the fallback method will be invoked. After a waiting period of 5 seconds, specified by waitDurationInOpenState
, the Circuit Breaker will automatically transition to the Half-Open state, thanks to automaticTransitionFromOpenToHalfOpenEnabled
being set to true. In the Half-Open state, up to 3 test calls are allowed to pass through to check if the external service has recovered. If these test calls fail, the Circuit Breaker will revert to the Open state and wait for another 5 seconds before retrying. If the test calls succeed, the Circuit Breaker will transition to the Closed state, resuming normal operations. The eventConsumerBufferSize
of 10 specifies the buffer size for storing Circuit Breaker events.