EventCountCircuitBreaker.java

  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. import java.beans.PropertyChangeListener;
  19. import java.util.EnumMap;
  20. import java.util.Map;
  21. import java.util.concurrent.TimeUnit;
  22. import java.util.concurrent.atomic.AtomicReference;

  23. /**
  24.  * A simple implementation of the <a
  25.  * href="https://martinfowler.com/bliki/CircuitBreaker.html">Circuit Breaker</a> pattern
  26.  * that counts specific events.
  27.  *
  28.  * <p>
  29.  * A <em>circuit breaker</em> can be used to protect an application against unreliable
  30.  * services or unexpected load. A newly created {@link EventCountCircuitBreaker} object is
  31.  * initially in state <em>closed</em> meaning that no problem has been detected. When the
  32.  * application encounters specific events (like errors or service timeouts), it tells the
  33.  * circuit breaker to increment an internal counter. If the number of events reported in a
  34.  * specific time interval exceeds a configurable threshold, the circuit breaker changes
  35.  * into state <em>open</em>. This means that there is a problem with the associated sub
  36.  * system; the application should no longer call it, but give it some time to settle down.
  37.  * The circuit breaker can be configured to switch back to <em>closed</em> state after a
  38.  * certain time frame if the number of events received goes below a threshold.
  39.  * </p>
  40.  * <p>
  41.  * When a {@link EventCountCircuitBreaker} object is constructed the following parameters
  42.  * can be provided:
  43.  * </p>
  44.  * <ul>
  45.  * <li>A threshold for the number of events that causes a state transition to
  46.  * <em>open</em> state. If more events are received in the configured check interval, the
  47.  * circuit breaker switches to <em>open</em> state.</li>
  48.  * <li>The interval for checks whether the circuit breaker should open. So it is possible
  49.  * to specify something like "The circuit breaker should open if more than 10 errors are
  50.  * encountered in a minute."</li>
  51.  * <li>The same parameters can be specified for automatically closing the circuit breaker
  52.  * again, as in "If the number of requests goes down to 100 per minute, the circuit
  53.  * breaker should close itself again". Depending on the use case, it may make sense to use
  54.  * a slightly lower threshold for closing the circuit breaker than for opening it to avoid
  55.  * continuously flipping when the number of events received is close to the threshold.</li>
  56.  * </ul>
  57.  * <p>
  58.  * This class supports the following typical use cases:
  59.  * </p>
  60.  * <p>
  61.  * <strong>Protecting against load peaks</strong>
  62.  * </p>
  63.  * <p>
  64.  * Imagine you have a server which can handle a certain number of requests per minute.
  65.  * Suddenly, the number of requests increases significantly - maybe because a connected
  66.  * partner system is going mad or due to a denial of service attack. A
  67.  * {@link EventCountCircuitBreaker} can be configured to stop the application from
  68.  * processing requests when a sudden peak load is detected and to start request processing
  69.  * again when things calm down. The following code fragment shows a typical example of
  70.  * such a scenario. Here the {@link EventCountCircuitBreaker} allows up to 1000 requests
  71.  * per minute before it interferes. When the load goes down again to 800 requests per
  72.  * second it switches back to state <em>closed</em>:
  73.  * </p>
  74.  *
  75.  * <pre>
  76.  * EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(1000, 1, TimeUnit.MINUTE, 800);
  77.  * ...
  78.  * public void handleRequest(Request request) {
  79.  *     if (breaker.incrementAndCheckState()) {
  80.  *         // actually handle this request
  81.  *     } else {
  82.  *         // do something else, e.g. send an error code
  83.  *     }
  84.  * }
  85.  * </pre>
  86.  * <p>
  87.  * <strong>Deal with an unreliable service</strong>
  88.  * </p>
  89.  * <p>
  90.  * In this scenario, an application uses an external service which may fail from time to
  91.  * time. If there are too many errors, the service is considered down and should not be
  92.  * called for a while. This can be achieved using the following pattern - in this concrete
  93.  * example we accept up to 5 errors in 2 minutes; if this limit is reached, the service is
  94.  * given a rest time of 10 minutes:
  95.  * </p>
  96.  *
  97.  * <pre>
  98.  * EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(5, 2, TimeUnit.MINUTE, 5, 10, TimeUnit.MINUTE);
  99.  * ...
  100.  * public void handleRequest(Request request) {
  101.  *     if (breaker.checkState()) {
  102.  *         try {
  103.  *             service.doSomething();
  104.  *         } catch (ServiceException ex) {
  105.  *             breaker.incrementAndCheckState();
  106.  *         }
  107.  *     } else {
  108.  *         // return an error code, use an alternative service, etc.
  109.  *     }
  110.  * }
  111.  * </pre>
  112.  * <p>
  113.  * In addition to automatic state transitions, the state of a circuit breaker can be
  114.  * changed manually using the methods {@link #open()} and {@link #close()}. It is also
  115.  * possible to register {@link PropertyChangeListener} objects that get notified whenever
  116.  * a state transition occurs. This is useful, for instance to directly react on a freshly
  117.  * detected error condition.
  118.  * </p>
  119.  * <p>
  120.  * <em>Implementation notes:</em>
  121.  * </p>
  122.  * <ul>
  123.  * <li>This implementation uses non-blocking algorithms to update the internal counter and
  124.  * state. This should be pretty efficient if there is not too much contention.</li>
  125.  * <li>This implementation is not intended to operate as a high-precision timer in very
  126.  * short check intervals. It is deliberately kept simple to avoid complex and
  127.  * time-consuming state checks. It should work well in time intervals from a few seconds
  128.  * up to minutes and longer. If the intervals become too short, there might be race
  129.  * conditions causing spurious state transitions.</li>
  130.  * <li>The handling of check intervals is a bit simplistic. Therefore, there is no
  131.  * guarantee that the circuit breaker is triggered at a specific point in time; there may
  132.  * be some delay (less than a check interval).</li>
  133.  * </ul>
  134.  * @since 3.5
  135.  */
  136. public class EventCountCircuitBreaker extends AbstractCircuitBreaker<Integer> {

  137.     /**
  138.      * An internally used data class holding information about the checks performed by
  139.      * this class. Basically, the number of received events and the start time of the
  140.      * current check interval are stored.
  141.      */
  142.     private static final class CheckIntervalData {
  143.         /** The counter for events. */
  144.         private final int eventCount;

  145.         /** The start time of the current check interval. */
  146.         private final long checkIntervalStart;

  147.         /**
  148.          * Creates a new instance of {@link CheckIntervalData}.
  149.          *
  150.          * @param count the current count value
  151.          * @param intervalStart the start time of the check interval
  152.          */
  153.         CheckIntervalData(final int count, final long intervalStart) {
  154.             eventCount = count;
  155.             checkIntervalStart = intervalStart;
  156.         }

  157.         /**
  158.          * Returns the start time of the current check interval.
  159.          *
  160.          * @return the check interval start time
  161.          */
  162.         public long getCheckIntervalStart() {
  163.             return checkIntervalStart;
  164.         }

  165.         /**
  166.          * Returns the event counter.
  167.          *
  168.          * @return the number of received events
  169.          */
  170.         public int getEventCount() {
  171.             return eventCount;
  172.         }

  173.         /**
  174.          * Returns a new instance of {@link CheckIntervalData} with the event counter
  175.          * incremented by the given delta. If the delta is 0, this object is returned.
  176.          *
  177.          * @param delta the delta
  178.          * @return the updated instance
  179.          */
  180.         public CheckIntervalData increment(final int delta) {
  181.             return delta == 0 ? this : new CheckIntervalData(getEventCount() + delta,
  182.                     getCheckIntervalStart());
  183.         }
  184.     }

  185.     /**
  186.      * Internally used class for executing check logic based on the current state of the
  187.      * circuit breaker. Having this logic extracted into special classes avoids complex
  188.      * if-then-else cascades.
  189.      */
  190.     private abstract static class StateStrategy {
  191.         /**
  192.          * Obtains the check interval to applied for the represented state from the given
  193.          * {@link CircuitBreaker}.
  194.          *
  195.          * @param breaker the {@link CircuitBreaker}
  196.          * @return the check interval to be applied
  197.          */
  198.         protected abstract long fetchCheckInterval(EventCountCircuitBreaker breaker);

  199.         /**
  200.          * Returns a flag whether the end of the current check interval is reached.
  201.          *
  202.          * @param breaker the {@link CircuitBreaker}
  203.          * @param currentData the current state object
  204.          * @param now the current time
  205.          * @return a flag whether the end of the current check interval is reached
  206.          */
  207.         public boolean isCheckIntervalFinished(final EventCountCircuitBreaker breaker,
  208.                 final CheckIntervalData currentData, final long now) {
  209.             return now - currentData.getCheckIntervalStart() > fetchCheckInterval(breaker);
  210.         }

  211.         /**
  212.          * Checks whether the specified {@link CheckIntervalData} objects indicate that a
  213.          * state transition should occur. Here the logic which checks for thresholds
  214.          * depending on the current state is implemented.
  215.          *
  216.          * @param breaker the {@link CircuitBreaker}
  217.          * @param currentData the current {@link CheckIntervalData} object
  218.          * @param nextData the updated {@link CheckIntervalData} object
  219.          * @return a flag whether a state transition should be performed
  220.          */
  221.         public abstract boolean isStateTransition(EventCountCircuitBreaker breaker,
  222.                 CheckIntervalData currentData, CheckIntervalData nextData);
  223.     }

  224.     /**
  225.      * A specialized {@link StateStrategy} implementation for the state closed.
  226.      */
  227.     private static final class StateStrategyClosed extends StateStrategy {

  228.         /**
  229.          * {@inheritDoc}
  230.          */
  231.         @Override
  232.         protected long fetchCheckInterval(final EventCountCircuitBreaker breaker) {
  233.             return breaker.getOpeningInterval();
  234.         }

  235.         /**
  236.          * {@inheritDoc}
  237.          */
  238.         @Override
  239.         public boolean isStateTransition(final EventCountCircuitBreaker breaker,
  240.                 final CheckIntervalData currentData, final CheckIntervalData nextData) {
  241.             return nextData.getEventCount() > breaker.getOpeningThreshold();
  242.         }
  243.     }

  244.     /**
  245.      * A specialized {@link StateStrategy} implementation for the state open.
  246.      */
  247.     private static final class StateStrategyOpen extends StateStrategy {
  248.         /**
  249.          * {@inheritDoc}
  250.          */
  251.         @Override
  252.         protected long fetchCheckInterval(final EventCountCircuitBreaker breaker) {
  253.             return breaker.getClosingInterval();
  254.         }

  255.         /**
  256.          * {@inheritDoc}
  257.          */
  258.         @Override
  259.         public boolean isStateTransition(final EventCountCircuitBreaker breaker,
  260.                 final CheckIntervalData currentData, final CheckIntervalData nextData) {
  261.             return nextData.getCheckIntervalStart() != currentData
  262.                     .getCheckIntervalStart()
  263.                     && currentData.getEventCount() < breaker.getClosingThreshold();
  264.         }
  265.     }

  266.     /** A map for accessing the strategy objects for the different states. */
  267.     private static final Map<State, StateStrategy> STRATEGY_MAP = createStrategyMap();

  268.     /**
  269.      * Creates the map with strategy objects. It allows access for a strategy for a given
  270.      * state.
  271.      *
  272.      * @return the strategy map
  273.      */
  274.     private static Map<State, StateStrategy> createStrategyMap() {
  275.         final Map<State, StateStrategy> map = new EnumMap<>(State.class);
  276.         map.put(State.CLOSED, new StateStrategyClosed());
  277.         map.put(State.OPEN, new StateStrategyOpen());
  278.         return map;
  279.     }

  280.     /**
  281.      * Returns the {@link StateStrategy} object responsible for the given state.
  282.      *
  283.      * @param state the state
  284.      * @return the corresponding {@link StateStrategy}
  285.      * @throws CircuitBreakingException if the strategy cannot be resolved
  286.      */
  287.     private static StateStrategy stateStrategy(final State state) {
  288.         return STRATEGY_MAP.get(state);
  289.     }

  290.     /** Stores information about the current check interval. */
  291.     private final AtomicReference<CheckIntervalData> checkIntervalData;

  292.     /** The threshold for opening the circuit breaker. */
  293.     private final int openingThreshold;

  294.     /** The time interval for opening the circuit breaker. */
  295.     private final long openingInterval;

  296.     /** The threshold for closing the circuit breaker. */
  297.     private final int closingThreshold;

  298.     /** The time interval for closing the circuit breaker. */
  299.     private final long closingInterval;

  300.     /**
  301.      * Creates a new instance of {@link EventCountCircuitBreaker} which uses the same parameters for
  302.      * opening and closing checks.
  303.      *
  304.      * @param threshold the threshold for changing the status of the circuit breaker; if
  305.      * the number of events received in a check interval is greater than this value, the
  306.      * circuit breaker is opened; if it is lower than this value, it is closed again
  307.      * @param checkInterval the check interval for opening or closing the circuit breaker
  308.      * @param checkUnit the {@link TimeUnit} defining the check interval
  309.      */
  310.     public EventCountCircuitBreaker(final int threshold, final long checkInterval, final TimeUnit checkUnit) {
  311.         this(threshold, checkInterval, checkUnit, threshold);
  312.     }

  313.     /**
  314.      * Creates a new instance of {@link EventCountCircuitBreaker} with the same interval for opening
  315.      * and closing checks.
  316.      *
  317.      * @param openingThreshold the threshold for opening the circuit breaker; if this
  318.      * number of events is received in the time span determined by the check interval, the
  319.      * circuit breaker is opened
  320.      * @param checkInterval the check interval for opening or closing the circuit breaker
  321.      * @param checkUnit the {@link TimeUnit} defining the check interval
  322.      * @param closingThreshold the threshold for closing the circuit breaker; if the
  323.      * number of events received in the time span determined by the check interval goes
  324.      * below this threshold, the circuit breaker is closed again
  325.      */
  326.     public EventCountCircuitBreaker(final int openingThreshold, final long checkInterval, final TimeUnit checkUnit,
  327.                                     final int closingThreshold) {
  328.         this(openingThreshold, checkInterval, checkUnit, closingThreshold, checkInterval,
  329.                 checkUnit);
  330.     }

  331.     /**
  332.      * Creates a new instance of {@link EventCountCircuitBreaker} and initializes all properties for
  333.      * opening and closing it based on threshold values for events occurring in specific
  334.      * intervals.
  335.      *
  336.      * @param openingThreshold the threshold for opening the circuit breaker; if this
  337.      * number of events is received in the time span determined by the opening interval,
  338.      * the circuit breaker is opened
  339.      * @param openingInterval the interval for opening the circuit breaker
  340.      * @param openingUnit the {@link TimeUnit} defining the opening interval
  341.      * @param closingThreshold the threshold for closing the circuit breaker; if the
  342.      * number of events received in the time span determined by the closing interval goes
  343.      * below this threshold, the circuit breaker is closed again
  344.      * @param closingInterval the interval for closing the circuit breaker
  345.      * @param closingUnit the {@link TimeUnit} defining the closing interval
  346.      */
  347.     public EventCountCircuitBreaker(final int openingThreshold, final long openingInterval,
  348.                                     final TimeUnit openingUnit, final int closingThreshold, final long closingInterval,
  349.                                     final TimeUnit closingUnit) {
  350.         checkIntervalData = new AtomicReference<>(new CheckIntervalData(0, 0));
  351.         this.openingThreshold = openingThreshold;
  352.         this.openingInterval = openingUnit.toNanos(openingInterval);
  353.         this.closingThreshold = closingThreshold;
  354.         this.closingInterval = closingUnit.toNanos(closingInterval);
  355.     }

  356.     /**
  357.      * Changes the state of this circuit breaker and also initializes a new
  358.      * {@link CheckIntervalData} object.
  359.      *
  360.      * @param newState the new state to be set
  361.      */
  362.     private void changeStateAndStartNewCheckInterval(final State newState) {
  363.         changeState(newState);
  364.         checkIntervalData.set(new CheckIntervalData(0, nanoTime()));
  365.     }

  366.     /**
  367.      * {@inheritDoc}
  368.      * <p>
  369.      * This implementation checks the internal event counter against the
  370.      * threshold values and the check intervals. This may cause a state change of this
  371.      * circuit breaker.
  372.      * </p>
  373.      */
  374.     @Override
  375.     public boolean checkState() {
  376.         return performStateCheck(0);
  377.     }

  378.     /**
  379.      * {@inheritDoc}
  380.      * <p>
  381.      * A new check interval is started. If too many events are received in
  382.      * this interval, the circuit breaker changes again to state open. If this circuit
  383.      * breaker is already closed, this method has no effect, except that a new check
  384.      * interval is started.
  385.      * </p>
  386.      */
  387.     @Override
  388.     public void close() {
  389.         super.close();
  390.         checkIntervalData.set(new CheckIntervalData(0, nanoTime()));
  391.     }

  392.     /**
  393.      * Returns the interval (in nanoseconds) for checking for the closing threshold.
  394.      *
  395.      * @return the opening check interval
  396.      */
  397.     public long getClosingInterval() {
  398.         return closingInterval;
  399.     }

  400.     /**
  401.      * Returns the threshold value for closing the circuit breaker. If the number of
  402.      * events received in the time span determined by the closing interval goes below this
  403.      * threshold, the circuit breaker is closed again.
  404.      *
  405.      * @return the closing threshold
  406.      */
  407.     public int getClosingThreshold() {
  408.         return closingThreshold;
  409.     }

  410.     /**
  411.      * Returns the interval (in nanoseconds) for checking for the opening threshold.
  412.      *
  413.      * @return the opening check interval
  414.      */
  415.     public long getOpeningInterval() {
  416.         return openingInterval;
  417.     }

  418.     /**
  419.      * Returns the threshold value for opening the circuit breaker. If this number of
  420.      * events is received in the time span determined by the opening interval, the circuit
  421.      * breaker is opened.
  422.      *
  423.      * @return the opening threshold
  424.      */
  425.     public int getOpeningThreshold() {
  426.         return openingThreshold;
  427.     }

  428.     /**
  429.      * Increments the monitored value by <strong>1</strong> and performs a check of the current state of this
  430.      * circuit breaker. This method works like {@link #checkState()}, but the monitored
  431.      * value is incremented before the state check is performed.
  432.      *
  433.      * @return <strong>true</strong> if the circuit breaker is now closed;
  434.      * <strong>false</strong> otherwise
  435.      */
  436.     public boolean incrementAndCheckState() {
  437.         return incrementAndCheckState(1);
  438.     }

  439.     /**
  440.      * {@inheritDoc}
  441.      */
  442.     @Override
  443.     public boolean incrementAndCheckState(final Integer increment) {
  444.         return performStateCheck(increment);
  445.     }

  446.     /**
  447.      * Returns the current time in nanoseconds. This method is used to obtain the current
  448.      * time. This is needed to calculate the check intervals correctly.
  449.      *
  450.      * @return the current time in nanoseconds
  451.      */
  452.     long nanoTime() {
  453.         return System.nanoTime();
  454.     }

  455.     /**
  456.      * Calculates the next {@link CheckIntervalData} object based on the current data and
  457.      * the current state. The next data object takes the counter increment and the current
  458.      * time into account.
  459.      *
  460.      * @param increment the increment for the internal counter
  461.      * @param currentData the current check data object
  462.      * @param currentState the current state of the circuit breaker
  463.      * @param time the current time
  464.      * @return the updated {@link CheckIntervalData} object
  465.      */
  466.     private CheckIntervalData nextCheckIntervalData(final int increment,
  467.             final CheckIntervalData currentData, final State currentState, final long time) {
  468.         final CheckIntervalData nextData;
  469.         if (stateStrategy(currentState).isCheckIntervalFinished(this, currentData, time)) {
  470.             nextData = new CheckIntervalData(increment, time);
  471.         } else {
  472.             nextData = currentData.increment(increment);
  473.         }
  474.         return nextData;
  475.     }

  476.     /**
  477.      * {@inheritDoc}
  478.      * <p>
  479.      * This circuit breaker may close itself again if the number of events
  480.      * received during a check interval goes below the closing threshold. If this circuit
  481.      * breaker is already open, this method has no effect, except that a new check
  482.      * interval is started.
  483.      * </p>
  484.      */
  485.     @Override
  486.     public void open() {
  487.         super.open();
  488.         checkIntervalData.set(new CheckIntervalData(0, nanoTime()));
  489.     }

  490.     /**
  491.      * Actually checks the state of this circuit breaker and executes a state transition
  492.      * if necessary.
  493.      *
  494.      * @param increment the increment for the internal counter
  495.      * @return a flag whether the circuit breaker is now closed
  496.      */
  497.     private boolean performStateCheck(final int increment) {
  498.         CheckIntervalData currentData;
  499.         CheckIntervalData nextData;
  500.         State currentState;

  501.         do {
  502.             final long time = nanoTime();
  503.             currentState = state.get();
  504.             currentData = checkIntervalData.get();
  505.             nextData = nextCheckIntervalData(increment, currentData, currentState, time);
  506.         } while (!updateCheckIntervalData(currentData, nextData));

  507.         // This might cause a race condition if other changes happen in between!
  508.         // Refer to the header comment!
  509.         if (stateStrategy(currentState).isStateTransition(this, currentData, nextData)) {
  510.             currentState = currentState.oppositeState();
  511.             changeStateAndStartNewCheckInterval(currentState);
  512.         }
  513.         return !isOpen(currentState);
  514.     }

  515.     /**
  516.      * Updates the {@link CheckIntervalData} object. The current data object is replaced
  517.      * by the one modified by the last check. The return value indicates whether this was
  518.      * successful. If it is <strong>false</strong>, another thread interfered, and the
  519.      * whole operation has to be redone.
  520.      *
  521.      * @param currentData the current check data object
  522.      * @param nextData the replacing check data object
  523.      * @return a flag whether the update was successful
  524.      */
  525.     private boolean updateCheckIntervalData(final CheckIntervalData currentData,
  526.             final CheckIntervalData nextData) {
  527.         return currentData == nextData
  528.                 || checkIntervalData.compareAndSet(currentData, nextData);
  529.     }

  530. }