Skip to content

Commit b29bd66

Browse files
committed
iluwatar#1510 Improvments done in Circuit Breaker
1 parent 9088ac5 commit b29bd66

16 files changed

Lines changed: 754 additions & 252 deletions

circuit-breaker/README.md

Lines changed: 162 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -52,40 +52,105 @@ In terms of code, the end user application is:
5252

5353
```java
5454
public class App {
55-
55+
5656
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
57-
57+
58+
/**
59+
* Program entry point.
60+
*
61+
* @param args command line args
62+
*/
5863
public static void main(String[] args) {
59-
var obj = new MonitoringService();
60-
var circuitBreaker = new CircuitBreaker(3000, 1, 2000 * 1000 * 1000);
64+
6165
var serverStartTime = System.nanoTime();
62-
while (true) {
63-
LOGGER.info(obj.localResourceResponse());
64-
LOGGER.info(obj.remoteResourceResponse(circuitBreaker, serverStartTime));
65-
LOGGER.info(circuitBreaker.getState());
66-
try {
67-
Thread.sleep(5 * 1000);
68-
} catch (InterruptedException e) {
69-
LOGGER.error(e.getMessage());
70-
}
66+
67+
var delayedService = new DelayedRemoteService(serverStartTime, 5);
68+
//Set the circuit Breaker parameters
69+
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2,
70+
2000 * 1000 * 1000);
71+
72+
var quickService = new QuickRemoteService();
73+
//Set the circuit Breaker parameters
74+
var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2,
75+
2000 * 1000 * 1000);
76+
77+
//Create an object of monitoring service which makes both local and remote calls
78+
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,
79+
quickServiceCircuitBreaker);
80+
81+
//Fetch response from local resource
82+
LOGGER.info(monitoringService.localResourceResponse());
83+
84+
//Fetch response from delayed service 2 times, to meet the failure threshold
85+
LOGGER.info(monitoringService.delayedServiceResponse());
86+
LOGGER.info(monitoringService.delayedServiceResponse());
87+
88+
//Fetch current state of delayed service circuit breaker after crossing failure threshold limit
89+
//which is OPEN now
90+
LOGGER.info(delayedServiceCircuitBreaker.getState());
91+
92+
//Meanwhile, the delayed service is down, fetch response from the healthy quick service
93+
LOGGER.info(monitoringService.quickServiceResponse());
94+
LOGGER.info(quickServiceCircuitBreaker.getState());
95+
96+
//Wait for the delayed service to become responsive
97+
try {
98+
Thread.sleep(5000);
99+
} catch (InterruptedException e) {
100+
e.printStackTrace();
71101
}
102+
//Check the state of delayed circuit breaker, should be HALF_OPEN
103+
LOGGER.info(delayedServiceCircuitBreaker.getState());
104+
105+
//Fetch response from delayed service, which should be healthy by now
106+
LOGGER.info(monitoringService.delayedServiceResponse());
107+
//As successful response is fetched, it should be CLOSED again.
108+
LOGGER.info(delayedServiceCircuitBreaker.getState());
72109
}
73110
}
74111
```
75112

76113
The monitoring service:
77114

78-
``` java
115+
```java
79116
public class MonitoringService {
80117

118+
private final CircuitBreaker delayedService;
119+
120+
private final CircuitBreaker quickService;
121+
122+
public MonitoringService(CircuitBreaker delayedService, CircuitBreaker quickService) {
123+
this.delayedService = delayedService;
124+
this.quickService = quickService;
125+
}
126+
127+
//Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic
81128
public String localResourceResponse() {
82129
return "Local Service is working";
83130
}
84131

85-
public String remoteResourceResponse(CircuitBreaker circuitBreaker, long serverStartTime) {
132+
/**
133+
* Fetch response from the delayed service (with some simulated startup time).
134+
*
135+
* @return response string
136+
*/
137+
public String delayedServiceResponse() {
86138
try {
87-
return circuitBreaker.call("delayedService", serverStartTime);
88-
} catch (Exception e) {
139+
return this.delayedService.attemptRequest();
140+
} catch (RemoteServiceException e) {
141+
return e.getMessage();
142+
}
143+
}
144+
145+
/**
146+
* Fetches response from a healthy service without any failure.
147+
*
148+
* @return response string
149+
*/
150+
public String quickServiceResponse() {
151+
try {
152+
return this.quickService.attemptRequest();
153+
} catch (RemoteServiceException e) {
89154
return e.getMessage();
90155
}
91156
}
@@ -95,76 +160,129 @@ As it can be seen, it does the call to get local resources directly, but it wrap
95160
remote (costly) service in a circuit breaker object, which prevents faults as follows:
96161

97162
```java
98-
public class CircuitBreaker {
163+
public class DefaultCircuitBreaker implements CircuitBreaker {
164+
99165
private final long timeout;
100166
private final long retryTimePeriod;
167+
private final RemoteService service;
101168
long lastFailureTime;
102169
int failureCount;
103170
private final int failureThreshold;
104171
private State state;
105172
private final long futureTime = 1000 * 1000 * 1000 * 1000;
106173

107-
CircuitBreaker(long timeout, int failureThreshold, long retryTimePeriod) {
174+
/**
175+
* Constructor to create an instance of Circuit Breaker.
176+
*
177+
* @param timeout Timeout for the API request. Not necessary for this simple example
178+
* @param failureThreshold Number of failures we receive from the depended service before changing
179+
* state to 'OPEN'
180+
* @param retryTimePeriod Time period after which a new request is made to remote service for
181+
* status check.
182+
*/
183+
DefaultCircuitBreaker(RemoteService serviceToCall, long timeout, int failureThreshold,
184+
long retryTimePeriod) {
185+
this.service = serviceToCall;
186+
// We start in a closed state hoping that everything is fine
108187
this.state = State.CLOSED;
109188
this.failureThreshold = failureThreshold;
189+
// Timeout for the API request.
190+
// Used to break the calls made to remote resource if it exceeds the limit
110191
this.timeout = timeout;
111192
this.retryTimePeriod = retryTimePeriod;
193+
//An absurd amount of time in future which basically indicates the last failure never happened
112194
this.lastFailureTime = System.nanoTime() + futureTime;
113195
this.failureCount = 0;
114196
}
115-
116-
private void reset() {
197+
198+
//Reset everything to defaults
199+
@Override
200+
public void recordSuccess() {
117201
this.failureCount = 0;
118-
this.lastFailureTime = System.nanoTime() + futureTime;
202+
this.lastFailureTime = System.nanoTime() + futureTime;
119203
this.state = State.CLOSED;
120204
}
121205

122-
private void recordFailure() {
206+
@Override
207+
public void recordFailure() {
123208
failureCount = failureCount + 1;
124209
this.lastFailureTime = System.nanoTime();
125210
}
126-
127-
protected void setState() {
128-
if (failureCount > failureThreshold) {
211+
212+
//Evaluate the current state based on failureThreshold, failureCount and lastFailureTime.
213+
protected void evaluateState() {
214+
if (failureCount >= failureThreshold) { //Then something is wrong with remote service
129215
if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) {
216+
//We have waited long enough and should try checking if service is up
130217
state = State.HALF_OPEN;
131218
} else {
219+
//Service would still probably be down
132220
state = State.OPEN;
133221
}
134222
} else {
223+
//Everything is working fine
135224
state = State.CLOSED;
136225
}
137226
}
138-
227+
228+
@Override
139229
public String getState() {
230+
evaluateState();
140231
return state.name();
141232
}
142-
143-
public void setStateForBypass(State state) {
233+
234+
/**
235+
* Break the circuit beforehand if it is known service is down Or connect the circuit manually if
236+
* service comes online before expected.
237+
*
238+
* @param state State at which circuit is in
239+
*/
240+
@Override
241+
public void setState(State state) {
144242
this.state = state;
243+
switch (state) {
244+
case OPEN:
245+
this.failureCount = failureThreshold;
246+
this.lastFailureTime = System.nanoTime();
247+
break;
248+
case HALF_OPEN:
249+
this.failureCount = failureThreshold;
250+
this.lastFailureTime = System.nanoTime() - retryTimePeriod;
251+
break;
252+
default:
253+
this.failureCount = 0;
254+
}
145255
}
146-
147-
public String call(String serviceToCall, long serverStartTime) throws Exception {
148-
setState();
256+
257+
/**
258+
* Executes service call.
259+
*
260+
* @return Value from the remote resource, stale response or a custom exception
261+
*/
262+
@Override
263+
public String attemptRequest() throws RemoteServiceException {
264+
evaluateState();
149265
if (state == State.OPEN) {
266+
// return cached response if no the circuit is in OPEN state
150267
return "This is stale response from API";
151268
} else {
152-
if (serviceToCall.equals("delayedService")) {
153-
var delayedService = new DelayedService(20);
154-
var response = delayedService.response(serverStartTime);
155-
if (response.split(" ")[3].equals("working")) {
156-
reset();
157-
return response;
158-
} else {
159-
recordFailure();
160-
throw new Exception("Remote service not responding");
161-
}
162-
} else {
163-
throw new Exception("Unknown Service Name");
269+
// Make the API request if the circuit is not OPEN
270+
try {
271+
//In a real application, this would be run in a thread and the timeout
272+
//parameter of the circuit breaker would be utilized to know if service
273+
//is working. Here, we simulate that based on server response itself
274+
var response = service.call();
275+
// Yay!! the API responded fine. Let's reset everything.
276+
recordSuccess();
277+
return response;
278+
} catch (RemoteServiceException ex) {
279+
recordFailure();
280+
throw ex;
164281
}
165282
}
166283
}
167284
}
285+
168286
```
169287

170288
How does the above pattern prevent failures? Let's understand via this finite state machine
33 KB
Loading

circuit-breaker/etc/circuit-breaker.urm.puml

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,51 @@ package com.iluwatar.circuitbreaker {
55
+ App()
66
+ main(args : String[]) {static}
77
}
8-
class CircuitBreaker {
8+
interface CircuitBreaker {
9+
+ attemptRequest() : String {abstract}
10+
+ getState() : String {abstract}
11+
+ recordFailure() {abstract}
12+
+ recordSuccess() {abstract}
13+
+ setState(State) {abstract}
14+
}
15+
class DefaultCircuitBreaker {
916
~ failureCount : int
1017
- failureThreshold : int
1118
- futureTime : long
1219
~ lastFailureTime : long
1320
- retryTimePeriod : long
21+
- service : RemoteService
1422
- state : State
1523
- timeout : long
16-
~ CircuitBreaker(timeout : long, failureThreshold : int, retryTimePeriod : long)
17-
+ call(serviceToCall : String, serverStartTime : long) : String
24+
~ DefaultCircuitBreaker(serviceToCall : RemoteService, timeout : long, failureThreshold : int, retryTimePeriod : long)
25+
+ attemptRequest() : String
26+
# evaluateState()
1827
+ getState() : String
19-
- recordFailure()
20-
- reset()
21-
# setState()
22-
+ setStateForBypass(state : State)
28+
+ recordFailure()
29+
+ recordSuccess()
30+
+ setState(state : State)
2331
}
24-
class DelayedService {
32+
class DelayedRemoteService {
2533
- delay : int
26-
+ DelayedService()
27-
+ DelayedService(delay : int)
28-
+ response(serverStartTime : long) : String
34+
- serverStartTime : long
35+
+ DelayedRemoteService()
36+
+ DelayedRemoteService(serverStartTime : long, delay : int)
37+
+ call() : String
2938
}
3039
class MonitoringService {
31-
+ MonitoringService()
40+
- delayedService : CircuitBreaker
41+
- quickService : CircuitBreaker
42+
+ MonitoringService(delayedService : CircuitBreaker, quickService : CircuitBreaker)
43+
+ delayedServiceResponse() : String
3244
+ localResourceResponse() : String
33-
+ remoteResourceResponse(circuitBreaker : CircuitBreaker, serverStartTime : long) : String
45+
+ quickServiceResponse() : String
46+
}
47+
class QuickRemoteService {
48+
+ QuickRemoteService()
49+
+ call() : String
50+
}
51+
interface RemoteService {
52+
+ call() : String {abstract}
3453
}
3554
enum State {
3655
+ CLOSED {static}
@@ -40,5 +59,10 @@ package com.iluwatar.circuitbreaker {
4059
+ values() : State[] {static}
4160
}
4261
}
43-
CircuitBreaker --> "-state" State
62+
DefaultCircuitBreaker --> "-state" State
63+
MonitoringService --> "-delayedService" CircuitBreaker
64+
DefaultCircuitBreaker --> "-service" RemoteService
65+
DefaultCircuitBreaker ..|> CircuitBreaker
66+
DelayedRemoteService ..|> RemoteService
67+
QuickRemoteService ..|> RemoteService
4468
@enduml

0 commit comments

Comments
 (0)