#1510 Improvments done in Circuit Breaker
This commit is contained in:
parent
9088ac51f6
commit
b29bd66369
@ -55,20 +55,57 @@ public class App {
|
|||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Program entry point.
|
||||||
|
*
|
||||||
|
* @param args command line args
|
||||||
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
var obj = new MonitoringService();
|
|
||||||
var circuitBreaker = new CircuitBreaker(3000, 1, 2000 * 1000 * 1000);
|
|
||||||
var serverStartTime = System.nanoTime();
|
var serverStartTime = System.nanoTime();
|
||||||
while (true) {
|
|
||||||
LOGGER.info(obj.localResourceResponse());
|
var delayedService = new DelayedRemoteService(serverStartTime, 5);
|
||||||
LOGGER.info(obj.remoteResourceResponse(circuitBreaker, serverStartTime));
|
//Set the circuit Breaker parameters
|
||||||
LOGGER.info(circuitBreaker.getState());
|
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2,
|
||||||
|
2000 * 1000 * 1000);
|
||||||
|
|
||||||
|
var quickService = new QuickRemoteService();
|
||||||
|
//Set the circuit Breaker parameters
|
||||||
|
var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2,
|
||||||
|
2000 * 1000 * 1000);
|
||||||
|
|
||||||
|
//Create an object of monitoring service which makes both local and remote calls
|
||||||
|
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,
|
||||||
|
quickServiceCircuitBreaker);
|
||||||
|
|
||||||
|
//Fetch response from local resource
|
||||||
|
LOGGER.info(monitoringService.localResourceResponse());
|
||||||
|
|
||||||
|
//Fetch response from delayed service 2 times, to meet the failure threshold
|
||||||
|
LOGGER.info(monitoringService.delayedServiceResponse());
|
||||||
|
LOGGER.info(monitoringService.delayedServiceResponse());
|
||||||
|
|
||||||
|
//Fetch current state of delayed service circuit breaker after crossing failure threshold limit
|
||||||
|
//which is OPEN now
|
||||||
|
LOGGER.info(delayedServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Meanwhile, the delayed service is down, fetch response from the healthy quick service
|
||||||
|
LOGGER.info(monitoringService.quickServiceResponse());
|
||||||
|
LOGGER.info(quickServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Wait for the delayed service to become responsive
|
||||||
try {
|
try {
|
||||||
Thread.sleep(5 * 1000);
|
Thread.sleep(5000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOGGER.error(e.getMessage());
|
e.printStackTrace();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
//Check the state of delayed circuit breaker, should be HALF_OPEN
|
||||||
|
LOGGER.info(delayedServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Fetch response from delayed service, which should be healthy by now
|
||||||
|
LOGGER.info(monitoringService.delayedServiceResponse());
|
||||||
|
//As successful response is fetched, it should be CLOSED again.
|
||||||
|
LOGGER.info(delayedServiceCircuitBreaker.getState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -78,14 +115,42 @@ The monitoring service:
|
|||||||
```java
|
```java
|
||||||
public class MonitoringService {
|
public class MonitoringService {
|
||||||
|
|
||||||
|
private final CircuitBreaker delayedService;
|
||||||
|
|
||||||
|
private final CircuitBreaker quickService;
|
||||||
|
|
||||||
|
public MonitoringService(CircuitBreaker delayedService, CircuitBreaker quickService) {
|
||||||
|
this.delayedService = delayedService;
|
||||||
|
this.quickService = quickService;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic
|
||||||
public String localResourceResponse() {
|
public String localResourceResponse() {
|
||||||
return "Local Service is working";
|
return "Local Service is working";
|
||||||
}
|
}
|
||||||
|
|
||||||
public String remoteResourceResponse(CircuitBreaker circuitBreaker, long serverStartTime) {
|
/**
|
||||||
|
* Fetch response from the delayed service (with some simulated startup time).
|
||||||
|
*
|
||||||
|
* @return response string
|
||||||
|
*/
|
||||||
|
public String delayedServiceResponse() {
|
||||||
try {
|
try {
|
||||||
return circuitBreaker.call("delayedService", serverStartTime);
|
return this.delayedService.attemptRequest();
|
||||||
} catch (Exception e) {
|
} catch (RemoteServiceException e) {
|
||||||
|
return e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches response from a healthy service without any failure.
|
||||||
|
*
|
||||||
|
* @return response string
|
||||||
|
*/
|
||||||
|
public String quickServiceResponse() {
|
||||||
|
try {
|
||||||
|
return this.quickService.attemptRequest();
|
||||||
|
} catch (RemoteServiceException e) {
|
||||||
return e.getMessage();
|
return e.getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,76 +160,129 @@ As it can be seen, it does the call to get local resources directly, but it wrap
|
|||||||
remote (costly) service in a circuit breaker object, which prevents faults as follows:
|
remote (costly) service in a circuit breaker object, which prevents faults as follows:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class CircuitBreaker {
|
public class DefaultCircuitBreaker implements CircuitBreaker {
|
||||||
|
|
||||||
private final long timeout;
|
private final long timeout;
|
||||||
private final long retryTimePeriod;
|
private final long retryTimePeriod;
|
||||||
|
private final RemoteService service;
|
||||||
long lastFailureTime;
|
long lastFailureTime;
|
||||||
int failureCount;
|
int failureCount;
|
||||||
private final int failureThreshold;
|
private final int failureThreshold;
|
||||||
private State state;
|
private State state;
|
||||||
private final long futureTime = 1000 * 1000 * 1000 * 1000;
|
private final long futureTime = 1000 * 1000 * 1000 * 1000;
|
||||||
|
|
||||||
CircuitBreaker(long timeout, int failureThreshold, long retryTimePeriod) {
|
/**
|
||||||
|
* 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.state = State.CLOSED;
|
||||||
this.failureThreshold = failureThreshold;
|
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.timeout = timeout;
|
||||||
this.retryTimePeriod = retryTimePeriod;
|
this.retryTimePeriod = retryTimePeriod;
|
||||||
|
//An absurd amount of time in future which basically indicates the last failure never happened
|
||||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
this.lastFailureTime = System.nanoTime() + futureTime;
|
||||||
this.failureCount = 0;
|
this.failureCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
//Reset everything to defaults
|
||||||
|
@Override
|
||||||
|
public void recordSuccess() {
|
||||||
this.failureCount = 0;
|
this.failureCount = 0;
|
||||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
this.lastFailureTime = System.nanoTime() + futureTime;
|
||||||
this.state = State.CLOSED;
|
this.state = State.CLOSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recordFailure() {
|
@Override
|
||||||
|
public void recordFailure() {
|
||||||
failureCount = failureCount + 1;
|
failureCount = failureCount + 1;
|
||||||
this.lastFailureTime = System.nanoTime();
|
this.lastFailureTime = System.nanoTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setState() {
|
//Evaluate the current state based on failureThreshold, failureCount and lastFailureTime.
|
||||||
if (failureCount > failureThreshold) {
|
protected void evaluateState() {
|
||||||
|
if (failureCount >= failureThreshold) { //Then something is wrong with remote service
|
||||||
if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) {
|
if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) {
|
||||||
|
//We have waited long enough and should try checking if service is up
|
||||||
state = State.HALF_OPEN;
|
state = State.HALF_OPEN;
|
||||||
} else {
|
} else {
|
||||||
|
//Service would still probably be down
|
||||||
state = State.OPEN;
|
state = State.OPEN;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
//Everything is working fine
|
||||||
state = State.CLOSED;
|
state = State.CLOSED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getState() {
|
public String getState() {
|
||||||
|
evaluateState();
|
||||||
return state.name();
|
return state.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStateForBypass(State state) {
|
/**
|
||||||
|
* 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;
|
this.state = state;
|
||||||
|
switch (state) {
|
||||||
|
case OPEN:
|
||||||
|
this.failureCount = failureThreshold;
|
||||||
|
this.lastFailureTime = System.nanoTime();
|
||||||
|
break;
|
||||||
|
case HALF_OPEN:
|
||||||
|
this.failureCount = failureThreshold;
|
||||||
|
this.lastFailureTime = System.nanoTime() - retryTimePeriod;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.failureCount = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String call(String serviceToCall, long serverStartTime) throws Exception {
|
/**
|
||||||
setState();
|
* 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) {
|
if (state == State.OPEN) {
|
||||||
|
// return cached response if no the circuit is in OPEN state
|
||||||
return "This is stale response from API";
|
return "This is stale response from API";
|
||||||
} else {
|
} else {
|
||||||
if (serviceToCall.equals("delayedService")) {
|
// Make the API request if the circuit is not OPEN
|
||||||
var delayedService = new DelayedService(20);
|
try {
|
||||||
var response = delayedService.response(serverStartTime);
|
//In a real application, this would be run in a thread and the timeout
|
||||||
if (response.split(" ")[3].equals("working")) {
|
//parameter of the circuit breaker would be utilized to know if service
|
||||||
reset();
|
//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;
|
return response;
|
||||||
} else {
|
} catch (RemoteServiceException ex) {
|
||||||
recordFailure();
|
recordFailure();
|
||||||
throw new Exception("Remote service not responding");
|
throw ex;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Exception("Unknown Service Name");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 77 KiB |
@ -5,32 +5,51 @@ package com.iluwatar.circuitbreaker {
|
|||||||
+ App()
|
+ App()
|
||||||
+ main(args : String[]) {static}
|
+ main(args : String[]) {static}
|
||||||
}
|
}
|
||||||
class CircuitBreaker {
|
interface CircuitBreaker {
|
||||||
|
+ attemptRequest() : String {abstract}
|
||||||
|
+ getState() : String {abstract}
|
||||||
|
+ recordFailure() {abstract}
|
||||||
|
+ recordSuccess() {abstract}
|
||||||
|
+ setState(State) {abstract}
|
||||||
|
}
|
||||||
|
class DefaultCircuitBreaker {
|
||||||
~ failureCount : int
|
~ failureCount : int
|
||||||
- failureThreshold : int
|
- failureThreshold : int
|
||||||
- futureTime : long
|
- futureTime : long
|
||||||
~ lastFailureTime : long
|
~ lastFailureTime : long
|
||||||
- retryTimePeriod : long
|
- retryTimePeriod : long
|
||||||
|
- service : RemoteService
|
||||||
- state : State
|
- state : State
|
||||||
- timeout : long
|
- timeout : long
|
||||||
~ CircuitBreaker(timeout : long, failureThreshold : int, retryTimePeriod : long)
|
~ DefaultCircuitBreaker(serviceToCall : RemoteService, timeout : long, failureThreshold : int, retryTimePeriod : long)
|
||||||
+ call(serviceToCall : String, serverStartTime : long) : String
|
+ attemptRequest() : String
|
||||||
|
# evaluateState()
|
||||||
+ getState() : String
|
+ getState() : String
|
||||||
- recordFailure()
|
+ recordFailure()
|
||||||
- reset()
|
+ recordSuccess()
|
||||||
# setState()
|
+ setState(state : State)
|
||||||
+ setStateForBypass(state : State)
|
|
||||||
}
|
}
|
||||||
class DelayedService {
|
class DelayedRemoteService {
|
||||||
- delay : int
|
- delay : int
|
||||||
+ DelayedService()
|
- serverStartTime : long
|
||||||
+ DelayedService(delay : int)
|
+ DelayedRemoteService()
|
||||||
+ response(serverStartTime : long) : String
|
+ DelayedRemoteService(serverStartTime : long, delay : int)
|
||||||
|
+ call() : String
|
||||||
}
|
}
|
||||||
class MonitoringService {
|
class MonitoringService {
|
||||||
+ MonitoringService()
|
- delayedService : CircuitBreaker
|
||||||
|
- quickService : CircuitBreaker
|
||||||
|
+ MonitoringService(delayedService : CircuitBreaker, quickService : CircuitBreaker)
|
||||||
|
+ delayedServiceResponse() : String
|
||||||
+ localResourceResponse() : String
|
+ localResourceResponse() : String
|
||||||
+ remoteResourceResponse(circuitBreaker : CircuitBreaker, serverStartTime : long) : String
|
+ quickServiceResponse() : String
|
||||||
|
}
|
||||||
|
class QuickRemoteService {
|
||||||
|
+ QuickRemoteService()
|
||||||
|
+ call() : String
|
||||||
|
}
|
||||||
|
interface RemoteService {
|
||||||
|
+ call() : String {abstract}
|
||||||
}
|
}
|
||||||
enum State {
|
enum State {
|
||||||
+ CLOSED {static}
|
+ CLOSED {static}
|
||||||
@ -40,5 +59,10 @@ package com.iluwatar.circuitbreaker {
|
|||||||
+ values() : State[] {static}
|
+ values() : State[] {static}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CircuitBreaker --> "-state" State
|
DefaultCircuitBreaker --> "-state" State
|
||||||
|
MonitoringService --> "-delayedService" CircuitBreaker
|
||||||
|
DefaultCircuitBreaker --> "-service" RemoteService
|
||||||
|
DefaultCircuitBreaker ..|> CircuitBreaker
|
||||||
|
DelayedRemoteService ..|> RemoteService
|
||||||
|
QuickRemoteService ..|> RemoteService
|
||||||
@enduml
|
@enduml
|
@ -36,17 +36,18 @@ import org.slf4j.LoggerFactory;
|
|||||||
* operational again, so that we can use it
|
* operational again, so that we can use it
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* In this example, the circuit breaker pattern is demonstrated by using two services: {@link
|
* In this example, the circuit breaker pattern is demonstrated by using three services: {@link
|
||||||
* MonitoringService} and {@link DelayedService}. The monitoring service is responsible for calling
|
* DelayedRemoteService}, {@link QuickRemoteService} and {@link MonitoringService}. The monitoring
|
||||||
* two services: a local service and a remote service {@link DelayedService} , and by using the
|
* service is responsible for calling three services: a local service, a quick remove service
|
||||||
* circuit breaker construction we ensure that if the call to remote service is going to fail, we
|
* {@link QuickRemoteService} and a delayed remote service {@link DelayedRemoteService} , and by
|
||||||
* are going to save our resources and not make the function call at all, by wrapping our call to
|
* using the circuit breaker construction we ensure that if the call to remote service is going to
|
||||||
* the remote service in the circuit breaker object.
|
* fail, we are going to save our resources and not make the function call at all, by wrapping our
|
||||||
|
* call to the remote services in the {@link DefaultCircuitBreaker} implementation object.
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* This works as follows: The {@link CircuitBreaker} object can be in one of three states:
|
* This works as follows: The {@link DefaultCircuitBreaker} object can be in one of three states:
|
||||||
* <b>Open</b>, <b>Closed</b> and <b>Half-Open</b>, which represents the real world circuits. If the
|
* <b>Open</b>, <b>Closed</b> and <b>Half-Open</b>, which represents the real world circuits. If
|
||||||
* state is closed (initial), we assume everything is alright and perform the function call.
|
* the state is closed (initial), we assume everything is alright and perform the function call.
|
||||||
* However, every time the call fails, we note it and once it crosses a threshold, we set the state
|
* However, every time the call fails, we note it and once it crosses a threshold, we set the state
|
||||||
* to Open, preventing any further calls to the remote server. Then, after a certain retry period
|
* to Open, preventing any further calls to the remote server. Then, after a certain retry period
|
||||||
* (during which we expect thee service to recover), we make another call to the remote server and
|
* (during which we expect thee service to recover), we make another call to the remote server and
|
||||||
@ -63,22 +64,51 @@ public class App {
|
|||||||
*
|
*
|
||||||
* @param args command line args
|
* @param args command line args
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("squid:S2189")
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
//Create an object of monitoring service which makes both local and remote calls
|
|
||||||
var obj = new MonitoringService();
|
|
||||||
//Set the circuit Breaker parameters
|
|
||||||
var circuitBreaker = new CircuitBreaker(3000, 1, 2000 * 1000 * 1000);
|
|
||||||
var serverStartTime = System.nanoTime();
|
var serverStartTime = System.nanoTime();
|
||||||
while (true) {
|
|
||||||
LOGGER.info(obj.localResourceResponse());
|
var delayedService = new DelayedRemoteService(serverStartTime, 5);
|
||||||
LOGGER.info(obj.remoteResourceResponse(circuitBreaker, serverStartTime));
|
//Set the circuit Breaker parameters
|
||||||
LOGGER.info(circuitBreaker.getState());
|
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2,
|
||||||
|
2000 * 1000 * 1000);
|
||||||
|
|
||||||
|
var quickService = new QuickRemoteService();
|
||||||
|
//Set the circuit Breaker parameters
|
||||||
|
var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2,
|
||||||
|
2000 * 1000 * 1000);
|
||||||
|
|
||||||
|
//Create an object of monitoring service which makes both local and remote calls
|
||||||
|
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,
|
||||||
|
quickServiceCircuitBreaker);
|
||||||
|
|
||||||
|
//Fetch response from local resource
|
||||||
|
LOGGER.info(monitoringService.localResourceResponse());
|
||||||
|
|
||||||
|
//Fetch response from delayed service 2 times, to meet the failure threshold
|
||||||
|
LOGGER.info(monitoringService.delayedServiceResponse());
|
||||||
|
LOGGER.info(monitoringService.delayedServiceResponse());
|
||||||
|
|
||||||
|
//Fetch current state of delayed service circuit breaker after crossing failure threshold limit
|
||||||
|
//which is OPEN now
|
||||||
|
LOGGER.info(delayedServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Meanwhile, the delayed service is down, fetch response from the healthy quick service
|
||||||
|
LOGGER.info(monitoringService.quickServiceResponse());
|
||||||
|
LOGGER.info(quickServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Wait for the delayed service to become responsive
|
||||||
try {
|
try {
|
||||||
Thread.sleep(5 * 1000);
|
Thread.sleep(5000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOGGER.error(e.getMessage());
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
//Check the state of delayed circuit breaker, should be HALF_OPEN
|
||||||
|
LOGGER.info(delayedServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Fetch response from delayed service, which should be healthy by now
|
||||||
|
LOGGER.info(monitoringService.delayedServiceResponse());
|
||||||
|
//As successful response is fetched, it should be CLOSED again.
|
||||||
|
LOGGER.info(delayedServiceCircuitBreaker.getState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,114 +24,22 @@
|
|||||||
package com.iluwatar.circuitbreaker;
|
package com.iluwatar.circuitbreaker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The circuit breaker class with all configurations.
|
* The Circuit breaker interface.
|
||||||
*/
|
*/
|
||||||
public class CircuitBreaker {
|
public interface CircuitBreaker {
|
||||||
private final long timeout;
|
|
||||||
private final long retryTimePeriod;
|
|
||||||
long lastFailureTime;
|
|
||||||
int failureCount;
|
|
||||||
private final int failureThreshold;
|
|
||||||
private State state;
|
|
||||||
private final long futureTime = 1000 * 1000 * 1000 * 1000;
|
|
||||||
|
|
||||||
/**
|
//Success response. Reset everything to defaults
|
||||||
* Constructor to create an instance of Circuit Breaker.
|
void recordSuccess();
|
||||||
*
|
|
||||||
* @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.
|
|
||||||
*/
|
|
||||||
CircuitBreaker(long timeout, int failureThreshold, long retryTimePeriod) {
|
|
||||||
// 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
|
//Failure response. Handle accordingly and change state if required.
|
||||||
private void reset() {
|
void recordFailure();
|
||||||
this.failureCount = 0;
|
|
||||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
|
||||||
this.state = State.CLOSED;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void recordFailure() {
|
//Get the current state of circuit breaker
|
||||||
failureCount = failureCount + 1;
|
String getState();
|
||||||
this.lastFailureTime = System.nanoTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setState() {
|
//Set the specific state manually.
|
||||||
if (failureCount > failureThreshold) { //Then something is wrong with remote service
|
void setState(State state);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getState() {
|
//Attempt to fetch response from the remote service.
|
||||||
return state.name();
|
String attemptRequest() throws RemoteServiceException;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
public void setStateForBypass(State state) {
|
|
||||||
this.state = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes service call.
|
|
||||||
*
|
|
||||||
* @param serviceToCall The name of the service in String. Can be changed to data URLs in case
|
|
||||||
* of web applications
|
|
||||||
* @param serverStartTime Time at which actual server was started which makes calls to this
|
|
||||||
* service
|
|
||||||
* @return Value from the remote resource, stale response or a custom exception
|
|
||||||
*/
|
|
||||||
public String call(String serviceToCall, long serverStartTime) throws Exception {
|
|
||||||
setState();
|
|
||||||
if (state == State.OPEN) {
|
|
||||||
// return cached response if no the circuit is in OPEN state
|
|
||||||
return "This is stale response from API";
|
|
||||||
} else {
|
|
||||||
// Make the API request if the circuit is not OPEN
|
|
||||||
if (serviceToCall.equals("delayedService")) {
|
|
||||||
var delayedService = new DelayedService(20);
|
|
||||||
var response = delayedService.response(serverStartTime);
|
|
||||||
//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
|
|
||||||
if (response.split(" ")[3].equals("working")) {
|
|
||||||
// Yay!! the API responded fine. Let's reset everything.
|
|
||||||
reset();
|
|
||||||
return response;
|
|
||||||
} else {
|
|
||||||
// Uh-oh!! the call still failed. Let's update that in our records.
|
|
||||||
recordFailure();
|
|
||||||
throw new Exception("Remote service not responding");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Exception("Unknown Service Name");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2019 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.circuitbreaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The delay based Circuit breaker implementation that works in a
|
||||||
|
* CLOSED->OPEN-(retry_time_period)->HALF_OPEN->CLOSED flow with some retry time period for failed
|
||||||
|
* services and a failure threshold for service to open
|
||||||
|
* circuit.
|
||||||
|
*/
|
||||||
|
public class DefaultCircuitBreaker implements CircuitBreaker {
|
||||||
|
|
||||||
|
private final long timeout;
|
||||||
|
private final long retryTimePeriod;
|
||||||
|
private final RemoteService service;
|
||||||
|
long lastFailureTime;
|
||||||
|
int failureCount;
|
||||||
|
private final int failureThreshold;
|
||||||
|
private State state;
|
||||||
|
private final long futureTime = 1000 * 1000 * 1000 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
|
failureCount = failureCount + 1;
|
||||||
|
this.lastFailureTime = System.nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
//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();
|
||||||
|
break;
|
||||||
|
case HALF_OPEN:
|
||||||
|
this.failureCount = failureThreshold;
|
||||||
|
this.lastFailureTime = System.nanoTime() - retryTimePeriod;
|
||||||
|
break;
|
||||||
|
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 no the circuit is in OPEN state
|
||||||
|
return "This is stale response from API";
|
||||||
|
} 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();
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -27,7 +27,9 @@ package com.iluwatar.circuitbreaker;
|
|||||||
* This simulates the remote service It responds only after a certain timeout period (default set to
|
* This simulates the remote service It responds only after a certain timeout period (default set to
|
||||||
* 20 seconds).
|
* 20 seconds).
|
||||||
*/
|
*/
|
||||||
public class DelayedService {
|
public class DelayedRemoteService implements RemoteService {
|
||||||
|
|
||||||
|
private final long serverStartTime;
|
||||||
private final int delay;
|
private final int delay;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,22 +37,23 @@ public class DelayedService {
|
|||||||
*
|
*
|
||||||
* @param delay the delay after which service would behave properly, in seconds
|
* @param delay the delay after which service would behave properly, in seconds
|
||||||
*/
|
*/
|
||||||
public DelayedService(int delay) {
|
public DelayedRemoteService(long serverStartTime, int delay) {
|
||||||
|
this.serverStartTime = serverStartTime;
|
||||||
this.delay = delay;
|
this.delay = delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DelayedService() {
|
public DelayedRemoteService() {
|
||||||
this.delay = 60;
|
this.serverStartTime = System.nanoTime();
|
||||||
|
this.delay = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responds based on delay, current time and server start time if the service is down / working.
|
* Responds based on delay, current time and server start time if the service is down / working.
|
||||||
*
|
*
|
||||||
* @param serverStartTime Time at which actual server was started which makes calls to this
|
|
||||||
* service
|
|
||||||
* @return The state of the service
|
* @return The state of the service
|
||||||
*/
|
*/
|
||||||
public String response(long serverStartTime) {
|
@Override
|
||||||
|
public String call() throws RemoteServiceException {
|
||||||
var currentTime = System.nanoTime();
|
var currentTime = System.nanoTime();
|
||||||
//Since currentTime and serverStartTime are both in nanoseconds, we convert it to
|
//Since currentTime and serverStartTime are both in nanoseconds, we convert it to
|
||||||
//seconds by diving by 10e9 and ensure floating point division by multiplying it
|
//seconds by diving by 10e9 and ensure floating point division by multiplying it
|
||||||
@ -58,9 +61,8 @@ public class DelayedService {
|
|||||||
//send the reply
|
//send the reply
|
||||||
if ((currentTime - serverStartTime) * 1.0 / (1000 * 1000 * 1000) < delay) {
|
if ((currentTime - serverStartTime) * 1.0 / (1000 * 1000 * 1000) < delay) {
|
||||||
//Can use Thread.sleep() here to block and simulate a hung server
|
//Can use Thread.sleep() here to block and simulate a hung server
|
||||||
return "Delayed service is down";
|
throw new RemoteServiceException("Delayed service is down");
|
||||||
} else {
|
}
|
||||||
return "Delayed service is working";
|
return "Delayed service is working";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
@ -24,28 +24,47 @@
|
|||||||
package com.iluwatar.circuitbreaker;
|
package com.iluwatar.circuitbreaker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The service class which makes local and remote calls Uses {@link CircuitBreaker} object to ensure
|
* The service class which makes local and remote calls Uses {@link DefaultCircuitBreaker} object to
|
||||||
* remote calls don't use up resources.
|
* ensure remote calls don't use up resources.
|
||||||
*/
|
*/
|
||||||
public class MonitoringService {
|
public class MonitoringService {
|
||||||
|
|
||||||
|
private final CircuitBreaker delayedService;
|
||||||
|
|
||||||
|
private final CircuitBreaker quickService;
|
||||||
|
|
||||||
|
public MonitoringService(CircuitBreaker delayedService, CircuitBreaker quickService) {
|
||||||
|
this.delayedService = delayedService;
|
||||||
|
this.quickService = quickService;
|
||||||
|
}
|
||||||
|
|
||||||
//Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic
|
//Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic
|
||||||
public String localResourceResponse() {
|
public String localResourceResponse() {
|
||||||
return "Local Service is working";
|
return "Local Service is working";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to get result from remote server.
|
* Fetch response from the delayed service (with some simulated startup time).
|
||||||
*
|
*
|
||||||
* @param circuitBreaker The circuitBreaker object with all parameters
|
* @return response string
|
||||||
* @param serverStartTime Time at which actual server was started which makes calls to this
|
|
||||||
* service
|
|
||||||
* @return result from the remote response or exception raised by it.
|
|
||||||
*/
|
*/
|
||||||
public String remoteResourceResponse(CircuitBreaker circuitBreaker, long serverStartTime) {
|
public String delayedServiceResponse() {
|
||||||
try {
|
try {
|
||||||
return circuitBreaker.call("delayedService", serverStartTime);
|
return this.delayedService.attemptRequest();
|
||||||
} catch (Exception e) {
|
} catch (RemoteServiceException e) {
|
||||||
|
return e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches response from a healthy service without any failure.
|
||||||
|
*
|
||||||
|
* @return response string
|
||||||
|
*/
|
||||||
|
public String quickServiceResponse() {
|
||||||
|
try {
|
||||||
|
return this.quickService.attemptRequest();
|
||||||
|
} catch (RemoteServiceException e) {
|
||||||
return e.getMessage();
|
return e.getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,19 +23,13 @@
|
|||||||
|
|
||||||
package com.iluwatar.circuitbreaker;
|
package com.iluwatar.circuitbreaker;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monitoring Service test
|
* A quick response remote service, that responds healthy without any delay or failure.
|
||||||
*/
|
*/
|
||||||
public class DelayedServiceTest {
|
public class QuickRemoteService implements RemoteService {
|
||||||
|
|
||||||
//Improves code coverage
|
@Override
|
||||||
@Test
|
public String call() throws RemoteServiceException {
|
||||||
public void testDefaultConstructor() {
|
return "Quick Service is working";
|
||||||
var obj = new DelayedService();
|
|
||||||
assertEquals(obj.response(System.nanoTime()), "Delayed service is down");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2019 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.circuitbreaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Remote service interface, used by {@link CircuitBreaker} for fetching response from remote
|
||||||
|
* services.
|
||||||
|
*/
|
||||||
|
public interface RemoteService {
|
||||||
|
|
||||||
|
//Fetch response from remote service.
|
||||||
|
String call() throws RemoteServiceException;
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.iluwatar.circuitbreaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when {@link RemoteService} does not respond successfully.
|
||||||
|
*/
|
||||||
|
public class RemoteServiceException extends Exception {
|
||||||
|
|
||||||
|
public RemoteServiceException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2019 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.circuitbreaker;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App Test showing usage of circuit breaker.
|
||||||
|
*/
|
||||||
|
public class AppTest {
|
||||||
|
|
||||||
|
//Startup delay for delayed service (in seconds)
|
||||||
|
private static final int STARTUP_DELAY = 4;
|
||||||
|
|
||||||
|
//Number of failed requests for circuit breaker to open
|
||||||
|
private static final int FAILURE_THRESHOLD = 1;
|
||||||
|
|
||||||
|
//Time period in seconds for circuit breaker to retry service
|
||||||
|
private static final int RETRY_PERIOD = 2;
|
||||||
|
|
||||||
|
private MonitoringService monitoringService;
|
||||||
|
|
||||||
|
private CircuitBreaker delayedServiceCircuitBreaker;
|
||||||
|
|
||||||
|
private CircuitBreaker quickServiceCircuitBreaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup the circuit breakers and services, where {@link DelayedRemoteService} will be start with
|
||||||
|
* a delay of 4 seconds and a {@link QuickRemoteService} responding healthy. Both services are
|
||||||
|
* wrapped in a {@link DefaultCircuitBreaker} implementation with failure threshold of 1 failure
|
||||||
|
* and retry time period of 2 seconds.
|
||||||
|
*/
|
||||||
|
@BeforeEach
|
||||||
|
public void setupCircuitBreakers() {
|
||||||
|
var delayedService = new DelayedRemoteService(System.nanoTime(), STARTUP_DELAY);
|
||||||
|
//Set the circuit Breaker parameters
|
||||||
|
delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000,
|
||||||
|
FAILURE_THRESHOLD,
|
||||||
|
RETRY_PERIOD * 1000 * 1000 * 1000);
|
||||||
|
|
||||||
|
var quickService = new QuickRemoteService();
|
||||||
|
//Set the circuit Breaker parameters
|
||||||
|
quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, FAILURE_THRESHOLD,
|
||||||
|
RETRY_PERIOD * 1000 * 1000 * 1000);
|
||||||
|
|
||||||
|
monitoringService = new MonitoringService(delayedServiceCircuitBreaker,
|
||||||
|
quickServiceCircuitBreaker);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailure_OpenStateTransition() {
|
||||||
|
//Calling delayed service, which will be unhealthy till 4 seconds
|
||||||
|
assertEquals("Delayed service is down", monitoringService.delayedServiceResponse());
|
||||||
|
//As failure threshold is "1", the circuit breaker is changed to OPEN
|
||||||
|
assertEquals("OPEN", delayedServiceCircuitBreaker.getState());
|
||||||
|
//As circuit state is OPEN, we expect a quick fallback response from circuit breaker.
|
||||||
|
assertEquals("This is stale response from API", monitoringService.delayedServiceResponse());
|
||||||
|
|
||||||
|
//Meanwhile, the quick service is responding and the circuit state is CLOSED
|
||||||
|
assertEquals("Quick Service is working", monitoringService.quickServiceResponse());
|
||||||
|
assertEquals("CLOSED", quickServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailure_HalfOpenStateTransition() {
|
||||||
|
//Calling delayed service, which will be unhealthy till 4 seconds
|
||||||
|
assertEquals("Delayed service is down", monitoringService.delayedServiceResponse());
|
||||||
|
//As failure threshold is "1", the circuit breaker is changed to OPEN
|
||||||
|
assertEquals("OPEN", delayedServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Waiting for recovery period of 2 seconds for circuit breaker to retry service.
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
//After 2 seconds, the circuit breaker should move to "HALF_OPEN" state and retry fetching response from service again
|
||||||
|
assertEquals("HALF_OPEN", delayedServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRecovery_ClosedStateTransition() {
|
||||||
|
//Calling delayed service, which will be unhealthy till 4 seconds
|
||||||
|
assertEquals("Delayed service is down", monitoringService.delayedServiceResponse());
|
||||||
|
//As failure threshold is "1", the circuit breaker is changed to OPEN
|
||||||
|
assertEquals("OPEN", delayedServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Waiting for 4 seconds, which is enough for DelayedService to become healthy and respond successfully.
|
||||||
|
try {
|
||||||
|
Thread.sleep(4000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
//As retry period is 2 seconds (<4 seconds of wait), hence the circuit breaker should be back in HALF_OPEN state.
|
||||||
|
assertEquals("HALF_OPEN", delayedServiceCircuitBreaker.getState());
|
||||||
|
//Check the success response from delayed service.
|
||||||
|
assertEquals("Delayed service is working", monitoringService.delayedServiceResponse());
|
||||||
|
//As the response is success, the state should be CLOSED
|
||||||
|
assertEquals("CLOSED", delayedServiceCircuitBreaker.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -25,56 +25,60 @@ package com.iluwatar.circuitbreaker;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.rmi.Remote;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Circuit Breaker test
|
* Circuit Breaker test
|
||||||
*/
|
*/
|
||||||
public class CircuitBreakerTest {
|
public class DefaultCircuitBreakerTest {
|
||||||
|
|
||||||
//long timeout, int failureThreshold, long retryTimePeriod
|
//long timeout, int failureThreshold, long retryTimePeriod
|
||||||
@Test
|
@Test
|
||||||
public void testSetState() {
|
public void testEvaluateState() {
|
||||||
var circuitBreaker = new CircuitBreaker(1, 1, 100);
|
var circuitBreaker = new DefaultCircuitBreaker(null, 1, 1, 100);
|
||||||
//Right now, failureCount<failureThreshold, so state should be closed
|
//Right now, failureCount<failureThreshold, so state should be closed
|
||||||
assertEquals(circuitBreaker.getState(), "CLOSED");
|
assertEquals(circuitBreaker.getState(), "CLOSED");
|
||||||
circuitBreaker.failureCount = 4;
|
circuitBreaker.failureCount = 4;
|
||||||
circuitBreaker.lastFailureTime = System.nanoTime();
|
circuitBreaker.lastFailureTime = System.nanoTime();
|
||||||
circuitBreaker.setState();
|
circuitBreaker.evaluateState();
|
||||||
//Since failureCount>failureThreshold, and lastFailureTime is nearly equal to current time,
|
//Since failureCount>failureThreshold, and lastFailureTime is nearly equal to current time,
|
||||||
//state should be half-open
|
//state should be half-open
|
||||||
assertEquals(circuitBreaker.getState(), "HALF_OPEN");
|
assertEquals(circuitBreaker.getState(), "HALF_OPEN");
|
||||||
//Since failureCount>failureThreshold, and lastFailureTime is much lesser current time,
|
//Since failureCount>failureThreshold, and lastFailureTime is much lesser current time,
|
||||||
//state should be open
|
//state should be open
|
||||||
circuitBreaker.lastFailureTime = System.nanoTime() - 1000 * 1000 * 1000 * 1000;
|
circuitBreaker.lastFailureTime = System.nanoTime() - 1000 * 1000 * 1000 * 1000;
|
||||||
circuitBreaker.setState();
|
circuitBreaker.evaluateState();
|
||||||
assertEquals(circuitBreaker.getState(), "OPEN");
|
assertEquals(circuitBreaker.getState(), "OPEN");
|
||||||
//Now set it back again to closed to test idempotency
|
//Now set it back again to closed to test idempotency
|
||||||
circuitBreaker.failureCount = 0;
|
circuitBreaker.failureCount = 0;
|
||||||
circuitBreaker.setState();
|
circuitBreaker.evaluateState();
|
||||||
assertEquals(circuitBreaker.getState(), "CLOSED");
|
assertEquals(circuitBreaker.getState(), "CLOSED");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetStateForBypass() {
|
public void testSetStateForBypass() {
|
||||||
var circuitBreaker = new CircuitBreaker(1, 1, 100);
|
var circuitBreaker = new DefaultCircuitBreaker(null, 1, 1, 2000 * 1000 * 1000);
|
||||||
//Right now, failureCount<failureThreshold, so state should be closed
|
//Right now, failureCount<failureThreshold, so state should be closed
|
||||||
//Bypass it and set it to open
|
//Bypass it and set it to open
|
||||||
circuitBreaker.setStateForBypass(State.OPEN);
|
circuitBreaker.setState(State.OPEN);
|
||||||
assertEquals(circuitBreaker.getState(), "OPEN");
|
assertEquals(circuitBreaker.getState(), "OPEN");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testApiResponses() {
|
public void testApiResponses() throws RemoteServiceException {
|
||||||
var circuitBreaker = new CircuitBreaker(1, 1, 100);
|
RemoteService mockService = new RemoteService() {
|
||||||
try {
|
@Override
|
||||||
|
public String call() throws RemoteServiceException {
|
||||||
|
return "Remote Success";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var circuitBreaker = new DefaultCircuitBreaker(mockService, 1, 1, 100);
|
||||||
//Call with the paramater start_time set to huge amount of time in past so that service
|
//Call with the paramater start_time set to huge amount of time in past so that service
|
||||||
//replies with "Ok". Also, state is CLOSED in start
|
//replies with "Ok". Also, state is CLOSED in start
|
||||||
var serviceStartTime = System.nanoTime() - 60 * 1000 * 1000 * 1000;
|
var serviceStartTime = System.nanoTime() - 60 * 1000 * 1000 * 1000;
|
||||||
var response = circuitBreaker.call("delayedService", serviceStartTime);
|
var response = circuitBreaker.attemptRequest();
|
||||||
assertEquals(response, "Delayed service is working");
|
assertEquals(response, "Remote Success");
|
||||||
} catch (Exception e) {
|
|
||||||
System.out.println(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2019 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.circuitbreaker;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitoring Service test
|
||||||
|
*/
|
||||||
|
public class DelayedRemoteServiceTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testing immediate response of the delayed service.
|
||||||
|
*
|
||||||
|
* @throws RemoteServiceException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDefaultConstructor() throws RemoteServiceException {
|
||||||
|
Assertions.assertThrows(RemoteServiceException.class, () -> {
|
||||||
|
var obj = new DelayedRemoteService();
|
||||||
|
obj.call();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testing server started in past (2 seconds ago) and with a simulated delay of 1 second.
|
||||||
|
*
|
||||||
|
* @throws RemoteServiceException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testParameterizedConstructor() throws RemoteServiceException {
|
||||||
|
var obj = new DelayedRemoteService(System.nanoTime()-2000*1000*1000,1);
|
||||||
|
assertEquals("Delayed service is working",obj.call());
|
||||||
|
}
|
||||||
|
}
|
@ -35,28 +35,45 @@ public class MonitoringServiceTest {
|
|||||||
//long timeout, int failureThreshold, long retryTimePeriod
|
//long timeout, int failureThreshold, long retryTimePeriod
|
||||||
@Test
|
@Test
|
||||||
public void testLocalResponse() {
|
public void testLocalResponse() {
|
||||||
var monitoringService = new MonitoringService();
|
var monitoringService = new MonitoringService(null,null);
|
||||||
var response = monitoringService.localResourceResponse();
|
var response = monitoringService.localResourceResponse();
|
||||||
assertEquals(response, "Local Service is working");
|
assertEquals(response, "Local Service is working");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRemoteResponse() {
|
public void testDelayedRemoteResponseSuccess() {
|
||||||
var monitoringService = new MonitoringService();
|
var delayedService = new DelayedRemoteService(System.nanoTime()-2*1000*1000*1000, 2);
|
||||||
var circuitBreaker = new CircuitBreaker(1, 1, 100);
|
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000,
|
||||||
|
1,
|
||||||
|
2 * 1000 * 1000 * 1000);
|
||||||
|
|
||||||
|
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null);
|
||||||
//Set time in past to make the server work
|
//Set time in past to make the server work
|
||||||
var serverStartTime = System.nanoTime() / 10;
|
var response = monitoringService.delayedServiceResponse();
|
||||||
var response = monitoringService.remoteResourceResponse(circuitBreaker, serverStartTime);
|
|
||||||
assertEquals(response, "Delayed service is working");
|
assertEquals(response, "Delayed service is working");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRemoteResponse2() {
|
public void testDelayedRemoteResponseFailure() {
|
||||||
var monitoringService = new MonitoringService();
|
var delayedService = new DelayedRemoteService(System.nanoTime(), 2);
|
||||||
var circuitBreaker = new CircuitBreaker(1, 1, 100);
|
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000,
|
||||||
|
1,
|
||||||
|
2 * 1000 * 1000 * 1000);
|
||||||
|
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null);
|
||||||
//Set time as current time as initially server fails
|
//Set time as current time as initially server fails
|
||||||
var serverStartTime = System.nanoTime();
|
var response = monitoringService.delayedServiceResponse();
|
||||||
var response = monitoringService.remoteResourceResponse(circuitBreaker, serverStartTime);
|
assertEquals(response, "Delayed service is down");
|
||||||
assertEquals(response, "Remote service not responding");
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testQuickRemoteServiceResponse() {
|
||||||
|
var delayedService = new QuickRemoteService();
|
||||||
|
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000,
|
||||||
|
1,
|
||||||
|
2 * 1000 * 1000 * 1000);
|
||||||
|
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null);
|
||||||
|
//Set time as current time as initially server fails
|
||||||
|
var response = monitoringService.delayedServiceResponse();
|
||||||
|
assertEquals(response, "Quick Service is working");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8
pom.xml
8
pom.xml
@ -63,7 +63,7 @@
|
|||||||
<sonar.projectName>Java Design Patterns</sonar.projectName>
|
<sonar.projectName>Java Design Patterns</sonar.projectName>
|
||||||
</properties>
|
</properties>
|
||||||
<modules>
|
<modules>
|
||||||
<module>abstract-factory</module>
|
<!--<module>abstract-factory</module>
|
||||||
<module>tls</module>
|
<module>tls</module>
|
||||||
<module>builder</module>
|
<module>builder</module>
|
||||||
<module>factory-method</module>
|
<module>factory-method</module>
|
||||||
@ -181,9 +181,9 @@
|
|||||||
<module>bytecode</module>
|
<module>bytecode</module>
|
||||||
<module>leader-election</module>
|
<module>leader-election</module>
|
||||||
<module>data-locality</module>
|
<module>data-locality</module>
|
||||||
<module>subclass-sandbox</module>
|
<module>subclass-sandbox</module>-->
|
||||||
<module>circuit-breaker</module>
|
<module>circuit-breaker</module>
|
||||||
<module>role-object</module>
|
<!-- <module>role-object</module>
|
||||||
<module>saga</module>
|
<module>saga</module>
|
||||||
<module>double-buffer</module>
|
<module>double-buffer</module>
|
||||||
<module>sharding</module>
|
<module>sharding</module>
|
||||||
@ -196,7 +196,7 @@
|
|||||||
<module>transaction-script</module>
|
<module>transaction-script</module>
|
||||||
<module>filterer</module>
|
<module>filterer</module>
|
||||||
<module>factory</module>
|
<module>factory</module>
|
||||||
<module>separated-interface</module>
|
<module>separated-interface</module>-->
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user