Circuit Breaker pattern is a pattern used in cases where you want the application to temporarily not call the services that are failing or the calls to these services are slow, disconnect them and try to reconnect as soon as the service has been restored. By defining 5 states of calling the services: OPEN, CLOSED, HALF_OPEN, DISABLED and FORCED_OPEN, the Circuit Breaker pattern will help us easily handle in case the application has errors when calling its service dependencies. You can implement the Circuit Breaker pattern using the Resilience4j CircuitBreaker library. How is it in detail? Let’s learn about the Circuit Breaker pattern and how to implement this pattern using the Resilience4j CircuitBreaker library in this tutorial!
First, I will create a new Maven project as an example:
You can declare Resilience4j CircuitBreaker dependency as follows:
1 2 3 4 5 |
<dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-circuitbreaker</artifactId> <version>2.2.0</version> </dependency> |
Talking about the 5 states defined in the CircuitBreaker pattern, I can roughly say as follows:
- OPEN: When implementing the CircuitBreaker pattern, we will configure a threshold so that when the call to the service dependencies fails, if the threshold is exceeded, the CircuitBreaker will be opened, preventing the call to these failed services. This threshold can be based on the number of times the call to the service failed or the time period in which the application calls the service failed.
- HALF_OPEN: After the CircuitBreaker is opened, after a certain period of time, the CircuitBreader will allow some requests to the services to be made to check if the service has worked again. If there are still errors, the OPEN state will continue to be maintained, but if the call to the service has no errors, the CircuitBreaker will automatically switch to the CLOSED state.
- CLOSED: as I said above, once the calls to the service dependencies are back to normal, CircuitBreaker will CLOSED and allow all requests to the services to operate normally.
- DISABLED: this is the state where CircuitBreaker is turned off, requests to the service dependencies will always be made.
- FORCE_OPEN: CircuitBreaker is always open and therefore, calls to the service dependencies will always be blocked.
Now I will use the Resilience4j CircuitBreaker library to implement the CircuitBreaker pattern for a simple application to see how it goes.
Suppose I have a simple application with a Hello class with a hello() method that returns the string “Hello from Huong Dan Java” as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package com.huongdanjava.resilience4j; public class Hello { private int failedTimes; private int maximumFailedTimes; public Hello(int maximumFailedTimes) { this.maximumFailedTimes = maximumFailedTimes; } public String hello() { System.out.println("hello() called ..."); if (failedTimes < maximumFailedTimes) { failedTimes++; throw new RuntimeException("Something went wrong"); } return "Hello from Huong Dan Java"; } } |
To simulate that the service dependency will sometimes be disconnected for a certain period of time, I defined the Hello class with a constructor with a parameter maximumFailedTimes, meaning that when the application calls the hello() method of the Hello class, there will be a maximum number of failed times before everything goes normally.
The Resilience4j CircuitBreaker library uses the CircuitBreaker class to handle the states when the application calls the service dependencies. The CircuitBreaker class will be defined with configurations related to the threshold, when the CircuitBreaker will have the state of OPEN, when it is CLOSED, … Similar to the Resilience4j TimeLimiter library, the Resilience4j CircuitBreaker library also has the CircuitBreakerRegistry class to manage these CircuitBreaker objects.
We can initialize the CircuitBreakerRegistry object with the following default configurations:
1 |
CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); |
The CircuitBreaker class object is derived from the circuitBreakerRegistry object above and will have the following default configurations:
- failureRateThreshold: the percentage failure rate threshold. The default value of this configuration is 50%.
- slowCallDurationThreshold: the time threshold for which calls are considered slow. The default value for this configuration is 60s.
- slowCallRateThreshold: the percentage slow call threshold. CircuitBreaker will consider a call slow if the duration of the call is greater than the value of the slowCallDurationThreshold configuration. The value of this configuration is 100% and when the slow call rate exceeds this threshold, CircuitBreaker will move to the open state!
- permittedNumberOfCallsInHalfOpenState: the number of calls allowed through CircuitBreaker at the time its state is HALF_OPEN. The default value of the configuration is 10 calls.
- maxWaitDurationInHalfOpenState: the maximum waiting time for CircuitBreaker to be in the HALF_OPEN state before switching to the OPEN state. The default value of this configuration is 0ms. This means that CircuitBreaker will wait until the requests that are permitted to pass through are completed.
- slidingWindowType: this configuration is related to how we count the number of problematic requests before CircuitBreaker switches to the OPEN state to prevent these problems. There are 2 ways to count the number of faulty requests: COUNT_BASED and TIME_BASED. COUNT_BASED will count the number of faulty requests and TIME_BASED will be based on the time period of the faulty requests. The default value of this configuration is COUNT_BASED!
- slidingWindowSize: the number of faulty requests or the time period of the faulty requests in seconds, depending on the value you are configuring for slidingWindowType. The default value of this configuration is 100.
- minimumNumberOfCalls: the minimum number of calls before CircuitBreaker counts the number of failed or slow calls. The default value is 100.
- waitDurationInOpenState: the amount of time to wait before CircuitBreaker transitions from OPEN to HALF_OPEN. The default value is 60s.
- automaticTransitionFromOpenToHalfOpenEnabled: use this configuration if you want the transition from OPEN to HALF_OPEN to happen automatically. The default value of this configuration is false.
- recordExceptions: configures which Exceptions will be counted as errors.
- ignoreExceptions: configures which Exceptions will not be counted as errors.
- recordFailurePredicate: allows us to use Predicate to determine in which cases an Exception will be counted as an error. By default, all Exceptions will be counted as errors.
- ignoreExceptionPredicate: allows us to use Predicate to determine in which cases the Exception will be ignored, not considered an error. By default, no Exception is ignored.
You can use the CircuitBreakerConfig class to change these default configurations, for example:
1 2 3 4 5 6 |
CircuitBreakerConfig config = CircuitBreakerConfig.custom() .failureRateThreshold(30) .slidingWindowSize(5) .build(); CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config); |
Here, I am configuring the CircuitBreaker to go into an OPEN state if 30% of the 5 calls to the hello() method of the Hello object failed.
Now, we will get the CircuitBreaker object from the object of the CircuitBreakerRegistry class as follows:
1 |
CircuitBreaker circuitBreaker = registry.circuitBreaker("hello"); |
We will pass the name information that we want to give to the CircuitBreaker. Here, as you can see, I assigned the name to my CircuitBreaker object as “hello”.
Now we will use the static decorate…() methods of the CircuitBreaker class, passing the information about the CircuitBreaker object above to execute the method that calls the service dependency through the CircuitBreaker. For example, I use the static decorateSupplier() method as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
CircuitBreaker circuitBreaker = registry.circuitBreaker("hello"); Hello hello = new Hello(2); Supplier<String> decorated = CircuitBreaker.decorateSupplier(circuitBreaker, hello::hello); for (int i = 0; i < 20; i++) { System.out.println("CircuitBreaker state is " + circuitBreaker.getState()); try { String s = decorated.get(); System.out.println(s); } catch (Exception e) { System.out.println("Error: " + e.getMessage()); } } |
When CircuitBreaker calls the hello() method of the Hello class object, there will be 2 failed calls, the error rate is 40%. Compared to the threshold I configured above, which is 30%, CircuitBreaker will OPEN. Therefore, running this example application, you will see the following result:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
CircuitBreaker state is CLOSED hello() called ... Error: Something went wrong CircuitBreaker state is CLOSED hello() called ... Error: Something went wrong CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls |
After 5 calls, CircuitBreaker will go to the OPEN state and no further calls will be made to the hello() method.
Because I am configuring that only the first 2 calls to the hello() method will fail and subsequent requests will always succeed, and the default value of waitDurationInOpenState is 60s, so if I now reduce the time of waitDurationInOpenState to 2ms:
1 2 3 4 5 |
CircuitBreakerConfig config = CircuitBreakerConfig.custom() .failureRateThreshold(30) .slidingWindowSize(5) .waitDurationInOpenState(Duration.ofMillis(2)) .build(); |
Rerun the example, you will see, after 2ms, CircuitBreaker will switch to HALF_OPEN state and then CLOSED as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
CircuitBreaker state is CLOSED hello() called ... Error: Something went wrong CircuitBreaker state is CLOSED hello() called ... Error: Something went wrong CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java |
So we have successfully implemented the CircuitBreaker pattern using the Resilience4j CircuitBreaker library!