mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-10-11 12:19:03 +00:00
docs: collecting parameter docs + formatting
This commit is contained in:
1 parent
ea7bc2a4eb
commit
f80cc468b2
23 files changed
+1191
-794
No files matched your search
+154
-139
@@ -14,38 +14,41 @@ tag:
|
||||
|
||||
## Intent
|
||||
|
||||
The Circuit Breaker pattern aims to prevent a software system from making calls to a part of the system that is either failing or showing signs of distress. It is a way to gracefully degrade functionality when a dependent service is not responding, rather than failing completely.
|
||||
The Circuit Breaker pattern aims to prevent a software system from making calls to a part of the system that is either
|
||||
failing or showing signs of distress. It is a way to gracefully degrade functionality when a dependent service is not
|
||||
responding, rather than failing completely.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> Imagine a web application that has both local files/images and remote services that are used for
|
||||
> fetching data. These remote services may be either healthy and responsive at times, or may become
|
||||
> slow and unresponsive at some point of time due to variety of reasons. So if one of the remote
|
||||
> services is slow or not responding successfully, our application will try to fetch response from
|
||||
> the remote service using multiple threads/processes, soon all of them will hang (also called
|
||||
> [thread starvation](https://en.wikipedia.org/wiki/Starvation_(computer_science))) causing our entire web application to crash. We should be able to detect
|
||||
> this situation and show the user an appropriate message so that he/she can explore other parts of
|
||||
> the app unaffected by the remote service failure. Meanwhile, the other services that are working
|
||||
> Imagine a web application that has both local files/images and remote services that are used for
|
||||
> fetching data. These remote services may be either healthy and responsive at times, or may become
|
||||
> slow and unresponsive at some point of time due to variety of reasons. So if one of the remote
|
||||
> services is slow or not responding successfully, our application will try to fetch response from
|
||||
> the remote service using multiple threads/processes, soon all of them will hang (also called
|
||||
> [thread starvation](https://en.wikipedia.org/wiki/Starvation_(computer_science))) causing our entire web application
|
||||
> to crash. We should be able to detect
|
||||
> this situation and show the user an appropriate message so that he/she can explore other parts of
|
||||
> the app unaffected by the remote service failure. Meanwhile, the other services that are working
|
||||
> normally, should keep functioning unaffected by this failure.
|
||||
|
||||
In plain words
|
||||
|
||||
> Circuit Breaker allows graceful handling of failed remote services. It's especially useful when
|
||||
> all parts of our application are highly decoupled from each other, and failure of one component
|
||||
> Circuit Breaker allows graceful handling of failed remote services. It's especially useful when
|
||||
> all parts of our application are highly decoupled from each other, and failure of one component
|
||||
> doesn't mean the other parts will stop working.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> Circuit breaker is a design pattern used in modern software development. It is used to detect
|
||||
> failures and encapsulates the logic of preventing a failure from constantly recurring, during
|
||||
> Circuit breaker is a design pattern used in modern software development. It is used to detect
|
||||
> failures and encapsulates the logic of preventing a failure from constantly recurring, during
|
||||
> maintenance, temporary external system failure or unexpected system difficulties.
|
||||
|
||||
## Programmatic Example
|
||||
|
||||
So, how does this all come together? With the above example in mind we will imitate the
|
||||
functionality in a simple example. A monitoring service mimics the web app and makes both local and
|
||||
So, how does this all come together? With the above example in mind we will imitate the
|
||||
functionality in a simple example. A monitoring service mimics the web app and makes both local and
|
||||
remote calls.
|
||||
|
||||
The service architecture is as follows:
|
||||
@@ -55,6 +58,7 @@ The service architecture is as follows:
|
||||
In terms of code, the end user application is:
|
||||
|
||||
```java
|
||||
|
||||
@Slf4j
|
||||
public class App {
|
||||
|
||||
@@ -114,7 +118,7 @@ public class App {
|
||||
}
|
||||
```
|
||||
|
||||
The monitoring service:
|
||||
The monitoring service:
|
||||
|
||||
```java
|
||||
public class MonitoringService {
|
||||
@@ -160,147 +164,154 @@ public class MonitoringService {
|
||||
}
|
||||
}
|
||||
```
|
||||
As it can be seen, it does the call to get local resources directly, but it wraps the call to
|
||||
|
||||
As it can be seen, it does the call to get local resources directly, but it wraps the call to
|
||||
remote (costly) service in a circuit breaker object, which prevents faults as follows:
|
||||
|
||||
```java
|
||||
public class DefaultCircuitBreaker implements CircuitBreaker {
|
||||
|
||||
private final long timeout;
|
||||
private final long retryTimePeriod;
|
||||
private final RemoteService service;
|
||||
long lastFailureTime;
|
||||
private String lastFailureResponse;
|
||||
int failureCount;
|
||||
private final int failureThreshold;
|
||||
private State state;
|
||||
// Future time offset, in nanoseconds
|
||||
private final long futureTime = 1_000_000_000_000L;
|
||||
private final long timeout;
|
||||
private final long retryTimePeriod;
|
||||
private final RemoteService service;
|
||||
long lastFailureTime;
|
||||
private String lastFailureResponse;
|
||||
int failureCount;
|
||||
private final int failureThreshold;
|
||||
private State state;
|
||||
// Future time offset, in nanoseconds
|
||||
private final long futureTime = 1_000_000_000_000L;
|
||||
|
||||
/**
|
||||
* Constructor to create an instance of Circuit Breaker.
|
||||
*
|
||||
* @param timeout Timeout for the API request. Not necessary for this simple example
|
||||
* @param failureThreshold Number of failures we receive from the depended service before changing
|
||||
* state to 'OPEN'
|
||||
* @param retryTimePeriod Time period after which a new request is made to remote service for
|
||||
* status check.
|
||||
*/
|
||||
DefaultCircuitBreaker(RemoteService serviceToCall, long timeout, int failureThreshold,
|
||||
long retryTimePeriod) {
|
||||
this.service = serviceToCall;
|
||||
// We start in a closed state hoping that everything is fine
|
||||
this.state = State.CLOSED;
|
||||
this.failureThreshold = failureThreshold;
|
||||
// Timeout for the API request.
|
||||
// Used to break the calls made to remote resource if it exceeds the limit
|
||||
this.timeout = timeout;
|
||||
this.retryTimePeriod = retryTimePeriod;
|
||||
//An absurd amount of time in future which basically indicates the last failure never happened
|
||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
||||
this.failureCount = 0;
|
||||
/**
|
||||
* Constructor to create an instance of Circuit Breaker.
|
||||
*
|
||||
* @param timeout Timeout for the API request. Not necessary for this simple example
|
||||
* @param failureThreshold Number of failures we receive from the depended service before changing
|
||||
* state to 'OPEN'
|
||||
* @param retryTimePeriod Time period after which a new request is made to remote service for
|
||||
* status check.
|
||||
*/
|
||||
DefaultCircuitBreaker(RemoteService serviceToCall, long timeout, int failureThreshold,
|
||||
long retryTimePeriod) {
|
||||
this.service = serviceToCall;
|
||||
// We start in a closed state hoping that everything is fine
|
||||
this.state = State.CLOSED;
|
||||
this.failureThreshold = failureThreshold;
|
||||
// Timeout for the API request.
|
||||
// Used to break the calls made to remote resource if it exceeds the limit
|
||||
this.timeout = timeout;
|
||||
this.retryTimePeriod = retryTimePeriod;
|
||||
//An absurd amount of time in future which basically indicates the last failure never happened
|
||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
||||
this.failureCount = 0;
|
||||
}
|
||||
|
||||
// Reset everything to defaults
|
||||
@Override
|
||||
public void recordSuccess() {
|
||||
this.failureCount = 0;
|
||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
||||
this.state = State.CLOSED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordFailure(String response) {
|
||||
failureCount = failureCount + 1;
|
||||
this.lastFailureTime = System.nanoTime();
|
||||
// Cache the failure response for returning on open state
|
||||
this.lastFailureResponse = response;
|
||||
}
|
||||
|
||||
// Evaluate the current state based on failureThreshold, failureCount and lastFailureTime.
|
||||
protected void evaluateState() {
|
||||
if (failureCount >= failureThreshold) { //Then something is wrong with remote service
|
||||
if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) {
|
||||
//We have waited long enough and should try checking if service is up
|
||||
state = State.HALF_OPEN;
|
||||
} else {
|
||||
//Service would still probably be down
|
||||
state = State.OPEN;
|
||||
}
|
||||
} else {
|
||||
//Everything is working fine
|
||||
state = State.CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset everything to defaults
|
||||
@Override
|
||||
public void recordSuccess() {
|
||||
this.failureCount = 0;
|
||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
||||
this.state = State.CLOSED;
|
||||
}
|
||||
@Override
|
||||
public String getState() {
|
||||
evaluateState();
|
||||
return state.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordFailure(String response) {
|
||||
failureCount = failureCount + 1;
|
||||
/**
|
||||
* Break the circuit beforehand if it is known service is down Or connect the circuit manually if
|
||||
* service comes online before expected.
|
||||
*
|
||||
* @param state State at which circuit is in
|
||||
*/
|
||||
@Override
|
||||
public void setState(State state) {
|
||||
this.state = state;
|
||||
switch (state) {
|
||||
case OPEN -> {
|
||||
this.failureCount = failureThreshold;
|
||||
this.lastFailureTime = System.nanoTime();
|
||||
// Cache the failure response for returning on open state
|
||||
this.lastFailureResponse = response;
|
||||
}
|
||||
case HALF_OPEN -> {
|
||||
this.failureCount = failureThreshold;
|
||||
this.lastFailureTime = System.nanoTime() - retryTimePeriod;
|
||||
}
|
||||
default -> this.failureCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate the current state based on failureThreshold, failureCount and lastFailureTime.
|
||||
protected void evaluateState() {
|
||||
if (failureCount >= failureThreshold) { //Then something is wrong with remote service
|
||||
if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) {
|
||||
//We have waited long enough and should try checking if service is up
|
||||
state = State.HALF_OPEN;
|
||||
} else {
|
||||
//Service would still probably be down
|
||||
state = State.OPEN;
|
||||
}
|
||||
} else {
|
||||
//Everything is working fine
|
||||
state = State.CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getState() {
|
||||
evaluateState();
|
||||
return state.name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Break the circuit beforehand if it is known service is down Or connect the circuit manually if
|
||||
* service comes online before expected.
|
||||
*
|
||||
* @param state State at which circuit is in
|
||||
*/
|
||||
@Override
|
||||
public void setState(State state) {
|
||||
this.state = state;
|
||||
switch (state) {
|
||||
case OPEN -> {
|
||||
this.failureCount = failureThreshold;
|
||||
this.lastFailureTime = System.nanoTime();
|
||||
}
|
||||
case HALF_OPEN -> {
|
||||
this.failureCount = failureThreshold;
|
||||
this.lastFailureTime = System.nanoTime() - retryTimePeriod;
|
||||
}
|
||||
default -> this.failureCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes service call.
|
||||
*
|
||||
* @return Value from the remote resource, stale response or a custom exception
|
||||
*/
|
||||
@Override
|
||||
public String attemptRequest() throws RemoteServiceException {
|
||||
evaluateState();
|
||||
if (state == State.OPEN) {
|
||||
// return cached response if the circuit is in OPEN state
|
||||
return this.lastFailureResponse;
|
||||
} else {
|
||||
// Make the API request if the circuit is not OPEN
|
||||
try {
|
||||
//In a real application, this would be run in a thread and the timeout
|
||||
//parameter of the circuit breaker would be utilized to know if service
|
||||
//is working. Here, we simulate that based on server response itself
|
||||
var response = service.call();
|
||||
// Yay!! the API responded fine. Let's reset everything.
|
||||
recordSuccess();
|
||||
return response;
|
||||
} catch (RemoteServiceException ex) {
|
||||
recordFailure(ex.getMessage());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Executes service call.
|
||||
*
|
||||
* @return Value from the remote resource, stale response or a custom exception
|
||||
*/
|
||||
@Override
|
||||
public String attemptRequest() throws RemoteServiceException {
|
||||
evaluateState();
|
||||
if (state == State.OPEN) {
|
||||
// return cached response if the circuit is in OPEN state
|
||||
return this.lastFailureResponse;
|
||||
} else {
|
||||
// Make the API request if the circuit is not OPEN
|
||||
try {
|
||||
//In a real application, this would be run in a thread and the timeout
|
||||
//parameter of the circuit breaker would be utilized to know if service
|
||||
//is working. Here, we simulate that based on server response itself
|
||||
var response = service.call();
|
||||
// Yay!! the API responded fine. Let's reset everything.
|
||||
recordSuccess();
|
||||
return response;
|
||||
} catch (RemoteServiceException ex) {
|
||||
recordFailure(ex.getMessage());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
How does the above pattern prevent failures? Let's understand via this finite state machine
|
||||
How does the above pattern prevent failures? Let's understand via this finite state machine
|
||||
implemented by it.
|
||||
|
||||

|
||||
|
||||
- We initialize the Circuit Breaker object with certain parameters: `timeout`, `failureThreshold` and `retryTimePeriod` which help determine how resilient the API is.
|
||||
- We initialize the Circuit Breaker object with certain parameters: `timeout`, `failureThreshold` and `retryTimePeriod`
|
||||
which help determine how resilient the API is.
|
||||
- Initially, we are in the `closed` state and nos remote calls to the API have occurred.
|
||||
- Every time the call succeeds, we reset the state to as it was in the beginning.
|
||||
- If the number of failures cross a certain threshold, we move to the `open` state, which acts just like an open circuit and prevents remote service calls from being made, thus saving resources. (Here, we return the response called ```stale response from API```)
|
||||
- Once we exceed the retry timeout period, we move to the `half-open` state and make another call to the remote service again to check if the service is working so that we can serve fresh content. A failure sets it back to `open` state and another attempt is made after retry timeout period, while a success sets it to `closed` state so that everything starts working normally again.
|
||||
- If the number of failures cross a certain threshold, we move to the `open` state, which acts just like an open circuit
|
||||
and prevents remote service calls from being made, thus saving resources. (Here, we return the response
|
||||
called ```stale response from API```)
|
||||
- Once we exceed the retry timeout period, we move to the `half-open` state and make another call to the remote service
|
||||
again to check if the service is working so that we can serve fresh content. A failure sets it back to `open` state
|
||||
and another attempt is made after retry timeout period, while a success sets it to `closed` state so that everything
|
||||
starts working normally again.
|
||||
|
||||
## Class diagram
|
||||
|
||||
@@ -330,14 +341,18 @@ Benefits:
|
||||
|
||||
Trade-Offs:
|
||||
|
||||
* The complexity of the system increases as the pattern requires additional logic to detect failures and manage the state of the circuit breaker
|
||||
* May lead to system degradation if not properly configured, as legitimate requests might be blocked if the circuit is open
|
||||
* The complexity of the system increases as the pattern requires additional logic to detect failures and manage the
|
||||
state of the circuit breaker
|
||||
* May lead to system degradation if not properly configured, as legitimate requests might be blocked if the circuit is
|
||||
open
|
||||
* Requires careful tuning of thresholds and timeout periods to balance between responsiveness and protection
|
||||
|
||||
## Related Patterns
|
||||
|
||||
- [Retry Pattern](https://github.com/iluwatar/java-design-patterns/tree/master/retry): Can be used in conjunction with the Circuit Breaker pattern to retry failed operations before opening the circuit
|
||||
- [Bulkhead Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/bulkhead): Can be used to isolate different parts of the system to prevent failures from spreading across the system
|
||||
- [Retry Pattern](https://github.com/iluwatar/java-design-patterns/tree/master/retry): Can be used in conjunction with
|
||||
the Circuit Breaker pattern to retry failed operations before opening the circuit
|
||||
- [Bulkhead Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/bulkhead): Can be used to isolate
|
||||
different parts of the system to prevent failures from spreading across the system
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
Reference in new issue
Block a user