Source code
Description: source: package: org.apache.commons.lang3.concurrent, class: EventCountCircuitBreaker

    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.lang3.concurrent;
    018
    019import java.beans.PropertyChangeListener;
    020import java.util.EnumMap;
    021import java.util.Map;
    022import java.util.concurrent.TimeUnit;
    023import java.util.concurrent.atomic.AtomicReference;
    024
    025/**
    026 * A simple implementation of the <a
    027 * href="https://martinfowler.com/bliki/CircuitBreaker.html">Circuit Breaker</a> pattern
    028 * that counts specific events.
    029 *
    030 * <p>
    031 * A <em>circuit breaker</em> can be used to protect an application against unreliable
    032 * services or unexpected load. A newly created {@link EventCountCircuitBreaker} object is
    033 * initially in state <em>closed</em> meaning that no problem has been detected. When the
    034 * application encounters specific events (like errors or service timeouts), it tells the
    035 * circuit breaker to increment an internal counter. If the number of events reported in a
    036 * specific time interval exceeds a configurable threshold, the circuit breaker changes
    037 * into state <em>open</em>. This means that there is a problem with the associated sub
    038 * system; the application should no longer call it, but give it some time to settle down.
    039 * The circuit breaker can be configured to switch back to <em>closed</em> state after a
    040 * certain time frame if the number of events received goes below a threshold.
    041 * </p>
    042 * <p>
    043 * When a {@link EventCountCircuitBreaker} object is constructed the following parameters
    044 * can be provided:
    045 * </p>
    046 * <ul>
    047 * <li>A threshold for the number of events that causes a state transition to
    048 * <em>open</em> state. If more events are received in the configured check interval, the
    049 * circuit breaker switches to <em>open</em> state.</li>
    050 * <li>The interval for checks whether the circuit breaker should open. So it is possible
    051 * to specify something like "The circuit breaker should open if more than 10 errors are
    052 * encountered in a minute."</li>
    053 * <li>The same parameters can be specified for automatically closing the circuit breaker
    054 * again, as in "If the number of requests goes down to 100 per minute, the circuit
    055 * breaker should close itself again". Depending on the use case, it may make sense to use
    056 * a slightly lower threshold for closing the circuit breaker than for opening it to avoid
    057 * continuously flipping when the number of events received is close to the threshold.</li>
    058 * </ul>
    059 * <p>
    060 * This class supports the following typical use cases:
    061 * </p>
    062 * <p>
    063 * <strong>Protecting against load peaks</strong>
    064 * </p>
    065 * <p>
    066 * Imagine you have a server which can handle a certain number of requests per minute.
    067 * Suddenly, the number of requests increases significantly - maybe because a connected
    068 * partner system is going mad or due to a denial of service attack. A
    069 * {@link EventCountCircuitBreaker} can be configured to stop the application from
    070 * processing requests when a sudden peak load is detected and to start request processing
    071 * again when things calm down. The following code fragment shows a typical example of
    072 * such a scenario. Here the {@link EventCountCircuitBreaker} allows up to 1000 requests
    073 * per minute before it interferes. When the load goes down again to 800 requests per
    074 * second it switches back to state <em>closed</em>:
    075 * </p>
    076 *
    077 * <pre>
    078 * EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(1000, 1, TimeUnit.MINUTE, 800);
    079 * ...
    080 * public void handleRequest(Request request) {
    081 *     if (breaker.incrementAndCheckState()) {
    082 *         // actually handle this request
    083 *     } else {
    084 *         // do something else, e.g. send an error code
    085 *     }
    086 * }
    087 * </pre>
    088 * <p>
    089 * <strong>Deal with an unreliable service</strong>
    090 * </p>
    091 * <p>
    092 * In this scenario, an application uses an external service which may fail from time to
    093 * time. If there are too many errors, the service is considered down and should not be
    094 * called for a while. This can be achieved using the following pattern - in this concrete
    095 * example we accept up to 5 errors in 2 minutes; if this limit is reached, the service is
    096 * given a rest time of 10 minutes:
    097 * </p>
    098 *
    099 * <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 */
    138public class EventCountCircuitBreaker extends AbstractCircuitBreaker<Integer> {
    139
    140    /**
    141     * Internally used class for executing check logic based on the current state of the
    142     * circuit breaker. Having this logic extracted into special classes avoids complex
    143     * if-then-else cascades.
    144     */
    145    private abstract static class AbstractStateStrategy {
    146        /**
    147         * Obtains the check interval to applied for the represented state from the given
    148         * {@link CircuitBreaker}.
    149         *
    150         * @param breaker the {@link CircuitBreaker}
    151         * @return the check interval to be applied
    152         */
    153        protected abstract long fetchCheckInterval(EventCountCircuitBreaker breaker);
    154
    155        /**
    156         * Tests whether the end of the current check interval is reached.
    157         *
    158         * @param breaker the {@link CircuitBreaker}
    159         * @param currentData the current state object
    160         * @param now the current time
    161         * @return a flag whether the end of the current check interval is reached
    162         */
    163        public boolean isCheckIntervalFinished(final EventCountCircuitBreaker breaker,
    164                final CheckIntervalData currentData, final long now) {
    165            return now - currentData.getCheckIntervalStart() > fetchCheckInterval(breaker);
    166        }
    167
    168        /**
    169         * Tests whether the specified {@link CheckIntervalData} objects indicate that a
    170         * state transition should occur. Here the logic which checks for thresholds
    171         * depending on the current state is implemented.
    172         *
    173         * @param breaker the {@link CircuitBreaker}
    174         * @param currentData the current {@link CheckIntervalData} object
    175         * @param nextData the updated {@link CheckIntervalData} object
    176         * @return a flag whether a state transition should be performed
    177         */
    178        public abstract boolean isStateTransition(EventCountCircuitBreaker breaker,
    179                CheckIntervalData currentData, CheckIntervalData nextData);
    180    }
    181
    182    /**
    183     * An internally used data class holding information about the checks performed by
    184     * this class. Basically, the number of received events and the start time of the
    185     * current check interval are stored.
    186     */
    187    private static final class CheckIntervalData {
    188        /** The counter for events. */
    189        private final int eventCount;
    190
    191        /** The start time of the current check interval. */
    192        private final long checkIntervalStart;
    193
    194        /**
    195         * Creates a new instance of {@link CheckIntervalData}.
    196         *
    197         * @param count the current count value
    198         * @param intervalStart the start time of the check interval
    199         */
    200        CheckIntervalData(final int count, final long intervalStart) {
    201            eventCount = count;
    202            checkIntervalStart = intervalStart;
    203        }
    204
    205        /**
    206         * Gets the start time of the current check interval.
    207         *
    208         * @return the check interval start time
    209         */
    210        public long getCheckIntervalStart() {
    211            return checkIntervalStart;
    212        }
    213
    214        /**
    215         * Gets the event counter.
    216         *
    217         * @return the number of received events
    218         */
    219        public int getEventCount() {
    220            return eventCount;
    221        }
    222
    223        /**
    224         * Returns a new instance of {@link CheckIntervalData} with the event counter
    225         * incremented by the given delta. If the delta is 0, this object is returned.
    226         *
    227         * @param delta the delta
    228         * @return the updated instance
    229         */
    230        public CheckIntervalData increment(final int delta) {
    231            return delta == 0 ? this : new CheckIntervalData(getEventCount() + delta,
    232                    getCheckIntervalStart());
    233        }
    234    }
    235
    236    /**
    237     * A specialized {@link AbstractStateStrategy} implementation for the state closed.
    238     */
    239    private static final class StateStrategyClosed extends AbstractStateStrategy {
    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 AbstractStateStrategy} implementation for the state open.
    261     */
    262    private static final class StateStrategyOpen extends AbstractStateStrategy {
    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, AbstractStateStrategy> 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, AbstractStateStrategy> createStrategyMap() {
    293        final Map<State, AbstractStateStrategy> 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 AbstractStateStrategy} object responsible for the given state.
    301     *
    302     * @param state the state
    303     * @return the corresponding {@link AbstractStateStrategy}
    304     * @throws CircuitBreakingException if the strategy cannot be resolved
    305     */
    306    private static AbstractStateStrategy 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     * Gets 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     * Gets 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     * Gets 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     * Gets 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}