View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3.concurrent;
18  
19  import java.beans.PropertyChangeListener;
20  import java.util.EnumMap;
21  import java.util.Map;
22  import java.util.concurrent.TimeUnit;
23  import java.util.concurrent.atomic.AtomicReference;
24  
25  /**
26   * A simple implementation of the <a
27   * href="https://martinfowler.com/bliki/CircuitBreaker.html">Circuit Breaker</a> pattern
28   * that counts specific events.
29   *
30   * <p>
31   * A <em>circuit breaker</em> can be used to protect an application against unreliable
32   * services or unexpected load. A newly created {@link EventCountCircuitBreaker} object is
33   * initially in state <em>closed</em> meaning that no problem has been detected. When the
34   * application encounters specific events (like errors or service timeouts), it tells the
35   * circuit breaker to increment an internal counter. If the number of events reported in a
36   * specific time interval exceeds a configurable threshold, the circuit breaker changes
37   * into state <em>open</em>. This means that there is a problem with the associated sub
38   * system; the application should no longer call it, but give it some time to settle down.
39   * The circuit breaker can be configured to switch back to <em>closed</em> state after a
40   * certain time frame if the number of events received goes below a threshold.
41   * </p>
42   * <p>
43   * When a {@link EventCountCircuitBreaker} object is constructed the following parameters
44   * can be provided:
45   * </p>
46   * <ul>
47   * <li>A threshold for the number of events that causes a state transition to
48   * <em>open</em> state. If more events are received in the configured check interval, the
49   * circuit breaker switches to <em>open</em> state.</li>
50   * <li>The interval for checks whether the circuit breaker should open. So it is possible
51   * to specify something like "The circuit breaker should open if more than 10 errors are
52   * encountered in a minute."</li>
53   * <li>The same parameters can be specified for automatically closing the circuit breaker
54   * again, as in "If the number of requests goes down to 100 per minute, the circuit
55   * breaker should close itself again". Depending on the use case, it may make sense to use
56   * a slightly lower threshold for closing the circuit breaker than for opening it to avoid
57   * continuously flipping when the number of events received is close to the threshold.</li>
58   * </ul>
59   * <p>
60   * This class supports the following typical use cases:
61   * </p>
62   * <p>
63   * <strong>Protecting against load peaks</strong>
64   * </p>
65   * <p>
66   * Imagine you have a server which can handle a certain number of requests per minute.
67   * Suddenly, the number of requests increases significantly - maybe because a connected
68   * partner system is going mad or due to a denial of service attack. A
69   * {@link EventCountCircuitBreaker} can be configured to stop the application from
70   * processing requests when a sudden peak load is detected and to start request processing
71   * again when things calm down. The following code fragment shows a typical example of
72   * such a scenario. Here the {@link EventCountCircuitBreaker} allows up to 1000 requests
73   * per minute before it interferes. When the load goes down again to 800 requests per
74   * second it switches back to state <em>closed</em>:
75   * </p>
76   *
77   * <pre>
78   * EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(1000, 1, TimeUnit.MINUTE, 800);
79   * ...
80   * public void handleRequest(Request request) {
81   *     if (breaker.incrementAndCheckState()) {
82   *         // actually handle this request
83   *     } else {
84   *         // do something else, e.g. send an error code
85   *     }
86   * }
87   * </pre>
88   * <p>
89   * <strong>Deal with an unreliable service</strong>
90   * </p>
91   * <p>
92   * In this scenario, an application uses an external service which may fail from time to
93   * time. If there are too many errors, the service is considered down and should not be
94   * called for a while. This can be achieved using the following pattern - in this concrete
95   * example we accept up to 5 errors in 2 minutes; if this limit is reached, the service is
96   * given a rest time of 10 minutes:
97   * </p>
98   *
99   * <pre>
100  * EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(5, 2, TimeUnit.MINUTE, 5, 10, TimeUnit.MINUTE);
101  * ...
102  * public void handleRequest(Request request) {
103  *     if (breaker.checkState()) {
104  *         try {
105  *             service.doSomething();
106  *         } catch (ServiceException ex) {
107  *             breaker.incrementAndCheckState();
108  *         }
109  *     } else {
110  *         // return an error code, use an alternative service, etc.
111  *     }
112  * }
113  * </pre>
114  * <p>
115  * In addition to automatic state transitions, the state of a circuit breaker can be
116  * changed manually using the methods {@link #open()} and {@link #close()}. It is also
117  * possible to register {@link PropertyChangeListener} objects that get notified whenever
118  * a state transition occurs. This is useful, for instance to directly react on a freshly
119  * detected error condition.
120  * </p>
121  * <p>
122  * <em>Implementation notes:</em>
123  * </p>
124  * <ul>
125  * <li>This implementation uses non-blocking algorithms to update the internal counter and
126  * state. This should be pretty efficient if there is not too much contention.</li>
127  * <li>This implementation is not intended to operate as a high-precision timer in very
128  * short check intervals. It is deliberately kept simple to avoid complex and
129  * time-consuming state checks. It should work well in time intervals from a few seconds
130  * up to minutes and longer. If the intervals become too short, there might be race
131  * conditions causing spurious state transitions.</li>
132  * <li>The handling of check intervals is a bit simplistic. Therefore, there is no
133  * guarantee that the circuit breaker is triggered at a specific point in time; there may
134  * be some delay (less than a check interval).</li>
135  * </ul>
136  * @since 3.5
137  */
138 public class EventCountCircuitBreaker extends AbstractCircuitBreaker<Integer> {
139 
140     /**
141      * An internally used data class holding information about the checks performed by
142      * this class. Basically, the number of received events and the start time of the
143      * current check interval are stored.
144      */
145     private static final class CheckIntervalData {
146         /** The counter for events. */
147         private final int eventCount;
148 
149         /** The start time of the current check interval. */
150         private final long checkIntervalStart;
151 
152         /**
153          * Creates a new instance of {@link CheckIntervalData}.
154          *
155          * @param count the current count value
156          * @param intervalStart the start time of the check interval
157          */
158         CheckIntervalData(final int count, final long intervalStart) {
159             eventCount = count;
160             checkIntervalStart = intervalStart;
161         }
162 
163         /**
164          * Returns the start time of the current check interval.
165          *
166          * @return the check interval start time
167          */
168         public long getCheckIntervalStart() {
169             return checkIntervalStart;
170         }
171 
172         /**
173          * Returns the event counter.
174          *
175          * @return the number of received events
176          */
177         public int getEventCount() {
178             return eventCount;
179         }
180 
181         /**
182          * Returns a new instance of {@link CheckIntervalData} with the event counter
183          * incremented by the given delta. If the delta is 0, this object is returned.
184          *
185          * @param delta the delta
186          * @return the updated instance
187          */
188         public CheckIntervalData increment(final int delta) {
189             return delta == 0 ? this : new CheckIntervalData(getEventCount() + delta,
190                     getCheckIntervalStart());
191         }
192     }
193 
194     /**
195      * Internally used class for executing check logic based on the current state of the
196      * circuit breaker. Having this logic extracted into special classes avoids complex
197      * if-then-else cascades.
198      */
199     private abstract static class StateStrategy {
200         /**
201          * Obtains the check interval to applied for the represented state from the given
202          * {@link CircuitBreaker}.
203          *
204          * @param breaker the {@link CircuitBreaker}
205          * @return the check interval to be applied
206          */
207         protected abstract long fetchCheckInterval(EventCountCircuitBreaker breaker);
208 
209         /**
210          * Returns a flag whether the end of the current check interval is reached.
211          *
212          * @param breaker the {@link CircuitBreaker}
213          * @param currentData the current state object
214          * @param now the current time
215          * @return a flag whether the end of the current check interval is reached
216          */
217         public boolean isCheckIntervalFinished(final EventCountCircuitBreaker breaker,
218                 final CheckIntervalData currentData, final long now) {
219             return now - currentData.getCheckIntervalStart() > fetchCheckInterval(breaker);
220         }
221 
222         /**
223          * Checks whether the specified {@link CheckIntervalData} objects indicate that a
224          * state transition should occur. Here the logic which checks for thresholds
225          * depending on the current state is implemented.
226          *
227          * @param breaker the {@link CircuitBreaker}
228          * @param currentData the current {@link CheckIntervalData} object
229          * @param nextData the updated {@link CheckIntervalData} object
230          * @return a flag whether a state transition should be performed
231          */
232         public abstract boolean isStateTransition(EventCountCircuitBreaker breaker,
233                 CheckIntervalData currentData, CheckIntervalData nextData);
234     }
235 
236     /**
237      * A specialized {@link StateStrategy} implementation for the state closed.
238      */
239     private static final class StateStrategyClosed extends StateStrategy {
240 
241         /**
242          * {@inheritDoc}
243          */
244         @Override
245         protected long fetchCheckInterval(final EventCountCircuitBreaker breaker) {
246             return breaker.getOpeningInterval();
247         }
248 
249         /**
250          * {@inheritDoc}
251          */
252         @Override
253         public boolean isStateTransition(final EventCountCircuitBreaker breaker,
254                 final CheckIntervalData currentData, final CheckIntervalData nextData) {
255             return nextData.getEventCount() > breaker.getOpeningThreshold();
256         }
257     }
258 
259     /**
260      * A specialized {@link StateStrategy} implementation for the state open.
261      */
262     private static final class StateStrategyOpen extends StateStrategy {
263         /**
264          * {@inheritDoc}
265          */
266         @Override
267         protected long fetchCheckInterval(final EventCountCircuitBreaker breaker) {
268             return breaker.getClosingInterval();
269         }
270 
271         /**
272          * {@inheritDoc}
273          */
274         @Override
275         public boolean isStateTransition(final EventCountCircuitBreaker breaker,
276                 final CheckIntervalData currentData, final CheckIntervalData nextData) {
277             return nextData.getCheckIntervalStart() != currentData
278                     .getCheckIntervalStart()
279                     && currentData.getEventCount() < breaker.getClosingThreshold();
280         }
281     }
282 
283     /** A map for accessing the strategy objects for the different states. */
284     private static final Map<State, StateStrategy> STRATEGY_MAP = createStrategyMap();
285 
286     /**
287      * Creates the map with strategy objects. It allows access for a strategy for a given
288      * state.
289      *
290      * @return the strategy map
291      */
292     private static Map<State, StateStrategy> createStrategyMap() {
293         final Map<State, StateStrategy> map = new EnumMap<>(State.class);
294         map.put(State.CLOSED, new StateStrategyClosed());
295         map.put(State.OPEN, new StateStrategyOpen());
296         return map;
297     }
298 
299     /**
300      * Returns the {@link StateStrategy} object responsible for the given state.
301      *
302      * @param state the state
303      * @return the corresponding {@link StateStrategy}
304      * @throws CircuitBreakingException if the strategy cannot be resolved
305      */
306     private static StateStrategy stateStrategy(final State state) {
307         return STRATEGY_MAP.get(state);
308     }
309 
310     /** Stores information about the current check interval. */
311     private final AtomicReference<CheckIntervalData> checkIntervalData;
312 
313     /** The threshold for opening the circuit breaker. */
314     private final int openingThreshold;
315 
316     /** The time interval for opening the circuit breaker. */
317     private final long openingInterval;
318 
319     /** The threshold for closing the circuit breaker. */
320     private final int closingThreshold;
321 
322     /** The time interval for closing the circuit breaker. */
323     private final long closingInterval;
324 
325     /**
326      * Creates a new instance of {@link EventCountCircuitBreaker} which uses the same parameters for
327      * opening and closing checks.
328      *
329      * @param threshold the threshold for changing the status of the circuit breaker; if
330      * the number of events received in a check interval is greater than this value, the
331      * circuit breaker is opened; if it is lower than this value, it is closed again
332      * @param checkInterval the check interval for opening or closing the circuit breaker
333      * @param checkUnit the {@link TimeUnit} defining the check interval
334      */
335     public EventCountCircuitBreaker(final int threshold, final long checkInterval, final TimeUnit checkUnit) {
336         this(threshold, checkInterval, checkUnit, threshold);
337     }
338 
339     /**
340      * Creates a new instance of {@link EventCountCircuitBreaker} with the same interval for opening
341      * and closing checks.
342      *
343      * @param openingThreshold the threshold for opening the circuit breaker; if this
344      * number of events is received in the time span determined by the check interval, the
345      * circuit breaker is opened
346      * @param checkInterval the check interval for opening or closing the circuit breaker
347      * @param checkUnit the {@link TimeUnit} defining the check interval
348      * @param closingThreshold the threshold for closing the circuit breaker; if the
349      * number of events received in the time span determined by the check interval goes
350      * below this threshold, the circuit breaker is closed again
351      */
352     public EventCountCircuitBreaker(final int openingThreshold, final long checkInterval, final TimeUnit checkUnit,
353                                     final int closingThreshold) {
354         this(openingThreshold, checkInterval, checkUnit, closingThreshold, checkInterval,
355                 checkUnit);
356     }
357 
358     /**
359      * Creates a new instance of {@link EventCountCircuitBreaker} and initializes all properties for
360      * opening and closing it based on threshold values for events occurring in specific
361      * intervals.
362      *
363      * @param openingThreshold the threshold for opening the circuit breaker; if this
364      * number of events is received in the time span determined by the opening interval,
365      * the circuit breaker is opened
366      * @param openingInterval the interval for opening the circuit breaker
367      * @param openingUnit the {@link TimeUnit} defining the opening interval
368      * @param closingThreshold the threshold for closing the circuit breaker; if the
369      * number of events received in the time span determined by the closing interval goes
370      * below this threshold, the circuit breaker is closed again
371      * @param closingInterval the interval for closing the circuit breaker
372      * @param closingUnit the {@link TimeUnit} defining the closing interval
373      */
374     public EventCountCircuitBreaker(final int openingThreshold, final long openingInterval,
375                                     final TimeUnit openingUnit, final int closingThreshold, final long closingInterval,
376                                     final TimeUnit closingUnit) {
377         checkIntervalData = new AtomicReference<>(new CheckIntervalData(0, 0));
378         this.openingThreshold = openingThreshold;
379         this.openingInterval = openingUnit.toNanos(openingInterval);
380         this.closingThreshold = closingThreshold;
381         this.closingInterval = closingUnit.toNanos(closingInterval);
382     }
383 
384     /**
385      * Changes the state of this circuit breaker and also initializes a new
386      * {@link CheckIntervalData} object.
387      *
388      * @param newState the new state to be set
389      */
390     private void changeStateAndStartNewCheckInterval(final State newState) {
391         changeState(newState);
392         checkIntervalData.set(new CheckIntervalData(0, nanoTime()));
393     }
394 
395     /**
396      * {@inheritDoc}
397      * <p>
398      * This implementation checks the internal event counter against the
399      * threshold values and the check intervals. This may cause a state change of this
400      * circuit breaker.
401      * </p>
402      */
403     @Override
404     public boolean checkState() {
405         return performStateCheck(0);
406     }
407 
408     /**
409      * {@inheritDoc}
410      * <p>
411      * A new check interval is started. If too many events are received in
412      * this interval, the circuit breaker changes again to state open. If this circuit
413      * breaker is already closed, this method has no effect, except that a new check
414      * interval is started.
415      * </p>
416      */
417     @Override
418     public void close() {
419         super.close();
420         checkIntervalData.set(new CheckIntervalData(0, nanoTime()));
421     }
422 
423     /**
424      * Returns the interval (in nanoseconds) for checking for the closing threshold.
425      *
426      * @return the opening check interval
427      */
428     public long getClosingInterval() {
429         return closingInterval;
430     }
431 
432     /**
433      * Returns the threshold value for closing the circuit breaker. If the number of
434      * events received in the time span determined by the closing interval goes below this
435      * threshold, the circuit breaker is closed again.
436      *
437      * @return the closing threshold
438      */
439     public int getClosingThreshold() {
440         return closingThreshold;
441     }
442 
443     /**
444      * Returns the interval (in nanoseconds) for checking for the opening threshold.
445      *
446      * @return the opening check interval
447      */
448     public long getOpeningInterval() {
449         return openingInterval;
450     }
451 
452     /**
453      * Returns the threshold value for opening the circuit breaker. If this number of
454      * events is received in the time span determined by the opening interval, the circuit
455      * breaker is opened.
456      *
457      * @return the opening threshold
458      */
459     public int getOpeningThreshold() {
460         return openingThreshold;
461     }
462 
463     /**
464      * Increments the monitored value by <strong>1</strong> and performs a check of the current state of this
465      * circuit breaker. This method works like {@link #checkState()}, but the monitored
466      * value is incremented before the state check is performed.
467      *
468      * @return <strong>true</strong> if the circuit breaker is now closed;
469      * <strong>false</strong> otherwise
470      */
471     public boolean incrementAndCheckState() {
472         return incrementAndCheckState(1);
473     }
474 
475     /**
476      * {@inheritDoc}
477      */
478     @Override
479     public boolean incrementAndCheckState(final Integer increment) {
480         return performStateCheck(increment);
481     }
482 
483     /**
484      * Returns the current time in nanoseconds. This method is used to obtain the current
485      * time. This is needed to calculate the check intervals correctly.
486      *
487      * @return the current time in nanoseconds
488      */
489     long nanoTime() {
490         return System.nanoTime();
491     }
492 
493     /**
494      * Calculates the next {@link CheckIntervalData} object based on the current data and
495      * the current state. The next data object takes the counter increment and the current
496      * time into account.
497      *
498      * @param increment the increment for the internal counter
499      * @param currentData the current check data object
500      * @param currentState the current state of the circuit breaker
501      * @param time the current time
502      * @return the updated {@link CheckIntervalData} object
503      */
504     private CheckIntervalData nextCheckIntervalData(final int increment,
505             final CheckIntervalData currentData, final State currentState, final long time) {
506         final CheckIntervalData nextData;
507         if (stateStrategy(currentState).isCheckIntervalFinished(this, currentData, time)) {
508             nextData = new CheckIntervalData(increment, time);
509         } else {
510             nextData = currentData.increment(increment);
511         }
512         return nextData;
513     }
514 
515     /**
516      * {@inheritDoc}
517      * <p>
518      * This circuit breaker may close itself again if the number of events
519      * received during a check interval goes below the closing threshold. If this circuit
520      * breaker is already open, this method has no effect, except that a new check
521      * interval is started.
522      * </p>
523      */
524     @Override
525     public void open() {
526         super.open();
527         checkIntervalData.set(new CheckIntervalData(0, nanoTime()));
528     }
529 
530     /**
531      * Actually checks the state of this circuit breaker and executes a state transition
532      * if necessary.
533      *
534      * @param increment the increment for the internal counter
535      * @return a flag whether the circuit breaker is now closed
536      */
537     private boolean performStateCheck(final int increment) {
538         CheckIntervalData currentData;
539         CheckIntervalData nextData;
540         State currentState;
541 
542         do {
543             final long time = nanoTime();
544             currentState = state.get();
545             currentData = checkIntervalData.get();
546             nextData = nextCheckIntervalData(increment, currentData, currentState, time);
547         } while (!updateCheckIntervalData(currentData, nextData));
548 
549         // This might cause a race condition if other changes happen in between!
550         // Refer to the header comment!
551         if (stateStrategy(currentState).isStateTransition(this, currentData, nextData)) {
552             currentState = currentState.oppositeState();
553             changeStateAndStartNewCheckInterval(currentState);
554         }
555         return !isOpen(currentState);
556     }
557 
558     /**
559      * Updates the {@link CheckIntervalData} object. The current data object is replaced
560      * by the one modified by the last check. The return value indicates whether this was
561      * successful. If it is <strong>false</strong>, another thread interfered, and the
562      * whole operation has to be redone.
563      *
564      * @param currentData the current check data object
565      * @param nextData the replacing check data object
566      * @return a flag whether the update was successful
567      */
568     private boolean updateCheckIntervalData(final CheckIntervalData currentData,
569             final CheckIntervalData nextData) {
570         return currentData == nextData
571                 || checkIntervalData.compareAndSet(currentData, nextData);
572     }
573 
574 }