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    *      https://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  *
137  * @since 3.5
138  */
139 public class EventCountCircuitBreaker extends AbstractCircuitBreaker<Integer> {
140 
141     /**
142      * Internally used class for executing check logic based on the current state of the
143      * circuit breaker. Having this logic extracted into special classes avoids complex
144      * if-then-else cascades.
145      */
146     private abstract static class AbstractStateStrategy {
147 
148         /**
149          * Obtains the check interval to applied for the represented state from the given
150          * {@link CircuitBreaker}.
151          *
152          * @param breaker the {@link CircuitBreaker}
153          * @return the check interval to be applied
154          */
155         protected abstract long fetchCheckInterval(EventCountCircuitBreaker breaker);
156 
157         /**
158          * Tests whether the end of the current check interval is reached.
159          *
160          * @param breaker the {@link CircuitBreaker}
161          * @param currentData the current state object
162          * @param now the current time
163          * @return a flag whether the end of the current check interval is reached
164          */
165         public boolean isCheckIntervalFinished(final EventCountCircuitBreaker breaker,
166                 final CheckIntervalData currentData, final long now) {
167             return now - currentData.getCheckIntervalStart() > fetchCheckInterval(breaker);
168         }
169 
170         /**
171          * Tests whether the specified {@link CheckIntervalData} objects indicate that a
172          * state transition should occur. Here the logic which checks for thresholds
173          * depending on the current state is implemented.
174          *
175          * @param breaker the {@link CircuitBreaker}
176          * @param currentData the current {@link CheckIntervalData} object
177          * @param nextData the updated {@link CheckIntervalData} object
178          * @return a flag whether a state transition should be performed
179          */
180         public abstract boolean isStateTransition(EventCountCircuitBreaker breaker,
181                 CheckIntervalData currentData, CheckIntervalData nextData);
182     }
183 
184     /**
185      * An internally used data class holding information about the checks performed by
186      * this class. Basically, the number of received events and the start time of the
187      * current check interval are stored.
188      */
189     private static final class CheckIntervalData {
190 
191         /** The counter for events. */
192         private final int eventCount;
193 
194         /** The start time of the current check interval. */
195         private final long checkIntervalStart;
196 
197         /**
198          * Creates a new instance of {@link CheckIntervalData}.
199          *
200          * @param count the current count value
201          * @param intervalStart the start time of the check interval
202          */
203         CheckIntervalData(final int count, final long intervalStart) {
204             eventCount = count;
205             checkIntervalStart = intervalStart;
206         }
207 
208         /**
209          * Gets the start time of the current check interval.
210          *
211          * @return the check interval start time
212          */
213         public long getCheckIntervalStart() {
214             return checkIntervalStart;
215         }
216 
217         /**
218          * Gets the event counter.
219          *
220          * @return the number of received events
221          */
222         public int getEventCount() {
223             return eventCount;
224         }
225 
226         /**
227          * Returns a new instance of {@link CheckIntervalData} with the event counter
228          * incremented by the given delta. If the delta is 0, this object is returned.
229          *
230          * @param delta the delta
231          * @return the updated instance
232          */
233         public CheckIntervalData increment(final int delta) {
234             return delta == 0 ? this : new CheckIntervalData(getEventCount() + delta,
235                     getCheckIntervalStart());
236         }
237     }
238 
239     /**
240      * A specialized {@link AbstractStateStrategy} implementation for the state closed.
241      */
242     private static final class StateStrategyClosed extends AbstractStateStrategy {
243 
244         /**
245          * {@inheritDoc}
246          */
247         @Override
248         protected long fetchCheckInterval(final EventCountCircuitBreaker breaker) {
249             return breaker.getOpeningInterval();
250         }
251 
252         /**
253          * {@inheritDoc}
254          */
255         @Override
256         public boolean isStateTransition(final EventCountCircuitBreaker breaker,
257                 final CheckIntervalData currentData, final CheckIntervalData nextData) {
258             return nextData.getEventCount() > breaker.getOpeningThreshold();
259         }
260     }
261 
262     /**
263      * A specialized {@link AbstractStateStrategy} implementation for the state open.
264      */
265     private static final class StateStrategyOpen extends AbstractStateStrategy {
266 
267         /**
268          * {@inheritDoc}
269          */
270         @Override
271         protected long fetchCheckInterval(final EventCountCircuitBreaker breaker) {
272             return breaker.getClosingInterval();
273         }
274 
275         /**
276          * {@inheritDoc}
277          */
278         @Override
279         public boolean isStateTransition(final EventCountCircuitBreaker breaker,
280                 final CheckIntervalData currentData, final CheckIntervalData nextData) {
281             return nextData.getCheckIntervalStart() != currentData
282                     .getCheckIntervalStart()
283                     && currentData.getEventCount() < breaker.getClosingThreshold();
284         }
285     }
286 
287     /** A map for accessing the strategy objects for the different states. */
288     private static final Map<State, AbstractStateStrategy> STRATEGY_MAP = createStrategyMap();
289 
290     /**
291      * Creates the map with strategy objects. It allows access for a strategy for a given
292      * state.
293      *
294      * @return the strategy map
295      */
296     private static Map<State, AbstractStateStrategy> createStrategyMap() {
297         final Map<State, AbstractStateStrategy> map = new EnumMap<>(State.class);
298         map.put(State.CLOSED, new StateStrategyClosed());
299         map.put(State.OPEN, new StateStrategyOpen());
300         return map;
301     }
302 
303     /**
304      * Returns the {@link AbstractStateStrategy} object responsible for the given state.
305      *
306      * @param state the state
307      * @return the corresponding {@link AbstractStateStrategy}
308      * @throws CircuitBreakingException if the strategy cannot be resolved
309      */
310     private static AbstractStateStrategy stateStrategy(final State state) {
311         return STRATEGY_MAP.get(state);
312     }
313 
314     /** Stores information about the current check interval. */
315     private final AtomicReference<CheckIntervalData> checkIntervalData;
316 
317     /** The threshold for opening the circuit breaker. */
318     private final int openingThreshold;
319 
320     /** The time interval for opening the circuit breaker. */
321     private final long openingInterval;
322 
323     /** The threshold for closing the circuit breaker. */
324     private final int closingThreshold;
325 
326     /** The time interval for closing the circuit breaker. */
327     private final long closingInterval;
328 
329     /**
330      * Creates a new instance of {@link EventCountCircuitBreaker} which uses the same parameters for
331      * opening and closing checks.
332      *
333      * @param threshold the threshold for changing the status of the circuit breaker; if
334      * the number of events received in a check interval is greater than this value, the
335      * circuit breaker is opened; if it is lower than this value, it is closed again
336      * @param checkInterval the check interval for opening or closing the circuit breaker
337      * @param checkUnit the {@link TimeUnit} defining the check interval
338      */
339     public EventCountCircuitBreaker(final int threshold, final long checkInterval, final TimeUnit checkUnit) {
340         this(threshold, checkInterval, checkUnit, threshold);
341     }
342 
343     /**
344      * Creates a new instance of {@link EventCountCircuitBreaker} with the same interval for opening
345      * and closing checks.
346      *
347      * @param openingThreshold the threshold for opening the circuit breaker; if this
348      * number of events is received in the time span determined by the check interval, the
349      * circuit breaker is opened
350      * @param checkInterval the check interval for opening or closing the circuit breaker
351      * @param checkUnit the {@link TimeUnit} defining the check interval
352      * @param closingThreshold the threshold for closing the circuit breaker; if the
353      * number of events received in the time span determined by the check interval goes
354      * below this threshold, the circuit breaker is closed again
355      */
356     public EventCountCircuitBreaker(final int openingThreshold, final long checkInterval, final TimeUnit checkUnit,
357                                     final int closingThreshold) {
358         this(openingThreshold, checkInterval, checkUnit, closingThreshold, checkInterval,
359                 checkUnit);
360     }
361 
362     /**
363      * Creates a new instance of {@link EventCountCircuitBreaker} and initializes all properties for
364      * opening and closing it based on threshold values for events occurring in specific
365      * intervals.
366      *
367      * @param openingThreshold the threshold for opening the circuit breaker; if this
368      * number of events is received in the time span determined by the opening interval,
369      * the circuit breaker is opened
370      * @param openingInterval the interval for opening the circuit breaker
371      * @param openingUnit the {@link TimeUnit} defining the opening interval
372      * @param closingThreshold the threshold for closing the circuit breaker; if the
373      * number of events received in the time span determined by the closing interval goes
374      * below this threshold, the circuit breaker is closed again
375      * @param closingInterval the interval for closing the circuit breaker
376      * @param closingUnit the {@link TimeUnit} defining the closing interval
377      */
378     public EventCountCircuitBreaker(final int openingThreshold, final long openingInterval,
379                                     final TimeUnit openingUnit, final int closingThreshold, final long closingInterval,
380                                     final TimeUnit closingUnit) {
381         checkIntervalData = new AtomicReference<>(new CheckIntervalData(0, 0));
382         this.openingThreshold = openingThreshold;
383         this.openingInterval = openingUnit.toNanos(openingInterval);
384         this.closingThreshold = closingThreshold;
385         this.closingInterval = closingUnit.toNanos(closingInterval);
386     }
387 
388     /**
389      * Changes the state of this circuit breaker and also initializes a new
390      * {@link CheckIntervalData} object.
391      *
392      * @param newState the new state to be set
393      */
394     private void changeStateAndStartNewCheckInterval(final State newState) {
395         changeState(newState);
396         checkIntervalData.set(new CheckIntervalData(0, nanoTime()));
397     }
398 
399     /**
400      * {@inheritDoc}
401      * <p>
402      * This implementation checks the internal event counter against the
403      * threshold values and the check intervals. This may cause a state change of this
404      * circuit breaker.
405      * </p>
406      */
407     @Override
408     public boolean checkState() {
409         return performStateCheck(0);
410     }
411 
412     /**
413      * {@inheritDoc}
414      * <p>
415      * A new check interval is started. If too many events are received in
416      * this interval, the circuit breaker changes again to state open. If this circuit
417      * breaker is already closed, this method has no effect, except that a new check
418      * interval is started.
419      * </p>
420      */
421     @Override
422     public void close() {
423         super.close();
424         checkIntervalData.set(new CheckIntervalData(0, nanoTime()));
425     }
426 
427     /**
428      * Gets the interval (in nanoseconds) for checking for the closing threshold.
429      *
430      * @return the opening check interval
431      */
432     public long getClosingInterval() {
433         return closingInterval;
434     }
435 
436     /**
437      * Gets the threshold value for closing the circuit breaker. If the number of
438      * events received in the time span determined by the closing interval goes below this
439      * threshold, the circuit breaker is closed again.
440      *
441      * @return the closing threshold
442      */
443     public int getClosingThreshold() {
444         return closingThreshold;
445     }
446 
447     /**
448      * Gets the interval (in nanoseconds) for checking for the opening threshold.
449      *
450      * @return the opening check interval
451      */
452     public long getOpeningInterval() {
453         return openingInterval;
454     }
455 
456     /**
457      * Gets the threshold value for opening the circuit breaker. If this number of
458      * events is received in the time span determined by the opening interval, the circuit
459      * breaker is opened.
460      *
461      * @return the opening threshold
462      */
463     public int getOpeningThreshold() {
464         return openingThreshold;
465     }
466 
467     /**
468      * Increments the monitored value by <strong>1</strong> and performs a check of the current state of this
469      * circuit breaker. This method works like {@link #checkState()}, but the monitored
470      * value is incremented before the state check is performed.
471      *
472      * @return <strong>true</strong> if the circuit breaker is now closed;
473      * <strong>false</strong> otherwise
474      */
475     public boolean incrementAndCheckState() {
476         return incrementAndCheckState(1);
477     }
478 
479     /**
480      * {@inheritDoc}
481      */
482     @Override
483     public boolean incrementAndCheckState(final Integer increment) {
484         return performStateCheck(increment);
485     }
486 
487     /**
488      * Returns the current time in nanoseconds. This method is used to obtain the current
489      * time. This is needed to calculate the check intervals correctly.
490      *
491      * @return the current time in nanoseconds
492      */
493     long nanoTime() {
494         return System.nanoTime();
495     }
496 
497     /**
498      * Calculates the next {@link CheckIntervalData} object based on the current data and
499      * the current state. The next data object takes the counter increment and the current
500      * time into account.
501      *
502      * @param increment the increment for the internal counter
503      * @param currentData the current check data object
504      * @param currentState the current state of the circuit breaker
505      * @param time the current time
506      * @return the updated {@link CheckIntervalData} object
507      */
508     private CheckIntervalData nextCheckIntervalData(final int increment,
509             final CheckIntervalData currentData, final State currentState, final long time) {
510         final CheckIntervalData nextData;
511         if (stateStrategy(currentState).isCheckIntervalFinished(this, currentData, time)) {
512             nextData = new CheckIntervalData(increment, time);
513         } else {
514             nextData = currentData.increment(increment);
515         }
516         return nextData;
517     }
518 
519     /**
520      * {@inheritDoc}
521      * <p>
522      * This circuit breaker may close itself again if the number of events
523      * received during a check interval goes below the closing threshold. If this circuit
524      * breaker is already open, this method has no effect, except that a new check
525      * interval is started.
526      * </p>
527      */
528     @Override
529     public void open() {
530         super.open();
531         checkIntervalData.set(new CheckIntervalData(0, nanoTime()));
532     }
533 
534     /**
535      * Actually checks the state of this circuit breaker and executes a state transition
536      * if necessary.
537      *
538      * @param increment the increment for the internal counter
539      * @return a flag whether the circuit breaker is now closed
540      */
541     private boolean performStateCheck(final int increment) {
542         CheckIntervalData currentData;
543         CheckIntervalData nextData;
544         State currentState;
545 
546         do {
547             final long time = nanoTime();
548             currentState = state.get();
549             currentData = checkIntervalData.get();
550             nextData = nextCheckIntervalData(increment, currentData, currentState, time);
551         } while (!updateCheckIntervalData(currentData, nextData));
552 
553         // This might cause a race condition if other changes happen in between!
554         // Refer to the header comment!
555         if (stateStrategy(currentState).isStateTransition(this, currentData, nextData)) {
556             currentState = currentState.oppositeState();
557             changeStateAndStartNewCheckInterval(currentState);
558         }
559         return !isOpen(currentState);
560     }
561 
562     /**
563      * Updates the {@link CheckIntervalData} object. The current data object is replaced
564      * by the one modified by the last check. The return value indicates whether this was
565      * successful. If it is <strong>false</strong>, another thread interfered, and the
566      * whole operation has to be redone.
567      *
568      * @param currentData the current check data object
569      * @param nextData the replacing check data object
570      * @return a flag whether the update was successful
571      */
572     private boolean updateCheckIntervalData(final CheckIntervalData currentData,
573             final CheckIntervalData nextData) {
574         return currentData == nextData
575                 || checkIntervalData.compareAndSet(currentData, nextData);
576     }
577 
578 }