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 }