All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.couchbase.client.core.endpoint.LazyCircuitBreaker Maven / Gradle / Ivy

/*
 * Copyright (c) 2018 Couchbase, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.couchbase.client.core.endpoint;

import com.couchbase.client.core.error.InvalidArgumentException;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

/**
 * This {@link CircuitBreaker} tracks its states in a lazy fashion.
 *
 * 

Lazy means that its state is evaluated at the time of a request, so there is no overhead * to be paid if no traffic is flowing through the associated endpoint.

* *

It works like this:

* *
    *
  • The circuit starts out as CLOSED, so operations can pass freely. Every * succeeding operation gets tracked towards a total rolling count, and after every configured window * it clears the counts for the next window.
  • *
  • If a response fails, then it is counted towards the failed requests as well and checked * if the circuit is over threshold and should be opened.
  • *
  • If the circuit trips, then it goes into an OPEN state. At this point, requests * are not allowed to go through until the sleep window elapses.
  • *
  • The next request can go through and sets it into HALF_OPEN. this request acts * as a canary! If it completes the circuit closes again. if it fails, then it goes back into * OPEN and the whole sleep process starts again.
  • *
* *

In addition, the endpoint can always {@link #reset()} its state, which usually happens * when the channel is reset.

* * @since 2.0.0 */ class LazyCircuitBreaker implements CircuitBreaker { /** * Current configuration. */ private final CircuitBreakerConfig config; /** * Duration in nanoseconds of the rolling window. */ private final long rollingWindow; /** * Time of the sleeping window in nanoseconds. */ private final long sleepingWindow; /** * Current state of this breaker. */ private final AtomicReference state; /** * Holds the base marker for the current tracking window as an absolute * nano timestamp. */ private volatile long windowStartTimestamp; /** * Counts all ops in the current window. */ private final AtomicLong totalInWindow; /** * Counts failed ops in the current window. */ private final AtomicLong failureInWindow; /** * Time in nanos when the circuit opened. */ private volatile long circuitOpened; /** * Creates a new {@link LazyCircuitBreaker}. * * @param config the config for this circuit breaker. */ LazyCircuitBreaker(final CircuitBreakerConfig config) { if (!config.enabled()) { throw InvalidArgumentException.fromMessage("This CircuitBreaker always needs to be enabled"); } this.config = config; this.state = new AtomicReference<>(); this.rollingWindow = config.rollingWindow().toNanos(); this.sleepingWindow = config.sleepWindow().toNanos(); this.totalInWindow = new AtomicLong(); this.failureInWindow = new AtomicLong(); reset(); } @Override public void track() { state.compareAndSet(State.OPEN, State.HALF_OPEN); } @Override public void reset() { final long now = System.nanoTime(); state.set(State.CLOSED); circuitOpened = now - sleepingWindow; totalInWindow.set(0); failureInWindow.set(0); windowStartTimestamp = now - rollingWindow; } @Override public boolean allowsRequest() { State state = state(); if (state == State.CLOSED) { return true; } boolean sleepingWindowElapsed = (System.nanoTime() - circuitOpened) > sleepingWindow; return state == State.OPEN && sleepingWindowElapsed; } @Override public State state() { return state.get(); } /** * Cleans up the current rolling window in case we rolled over. */ private void cleanRollingWindow() { long now = System.nanoTime(); if ((now - windowStartTimestamp) > rollingWindow) { windowStartTimestamp = now; totalInWindow.set(0); failureInWindow.set(0); } } /** * Checks if we have tripped and if so performs side effects to set the circuit * breaker into the right state. */ private void checkIfTripped() { if (totalInWindow.get() < config.volumeThreshold()) { return; } int percentThreshold = config.errorThresholdPercentage(); long currentThreshold = (long) ((failureInWindow.get() * 1.0f / totalInWindow.get()) * 100); if (currentThreshold >= percentThreshold) { state.set(State.OPEN); circuitOpened = System.nanoTime(); } } /** * Mark a tracked request as failed. */ @Override public void markFailure() { long now = System.nanoTime(); if (state.compareAndSet(State.HALF_OPEN, State.OPEN)) { circuitOpened = now; } else { cleanRollingWindow(); totalInWindow.incrementAndGet(); failureInWindow.incrementAndGet(); checkIfTripped(); } } /** * Mark a tracked request as success. */ @Override public void markSuccess() { if (state.compareAndSet(State.HALF_OPEN, State.CLOSED)) { reset(); } else { cleanRollingWindow(); totalInWindow.incrementAndGet(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy