Talk about CircuitBreakerConfig of resilience4j

  resilience4j

Order

This paper mainly studies CircuitBreakerConfig of resilience4j

CircuitBreakerConfig

resilience4j-circuitbreaker-0.13.0-sources.jar! /io/github/resilience4j/circuitbreaker/CircuitBreakerConfig.java

/**
 * A {@link CircuitBreakerConfig} configures a {@link CircuitBreaker}
 */
public class CircuitBreakerConfig {

    public static final int DEFAULT_MAX_FAILURE_THRESHOLD = 50; // Percentage
    public static final int DEFAULT_WAIT_DURATION_IN_OPEN_STATE = 60; // Seconds
    public static final int DEFAULT_RING_BUFFER_SIZE_IN_HALF_OPEN_STATE = 10;
    public static final int DEFAULT_RING_BUFFER_SIZE_IN_CLOSED_STATE = 100;
    public static final Predicate<Throwable> DEFAULT_RECORD_FAILURE_PREDICATE = (throwable) -> true;

    private float failureRateThreshold = DEFAULT_MAX_FAILURE_THRESHOLD;
    private int ringBufferSizeInHalfOpenState = DEFAULT_RING_BUFFER_SIZE_IN_HALF_OPEN_STATE;
    private int ringBufferSizeInClosedState = DEFAULT_RING_BUFFER_SIZE_IN_CLOSED_STATE;
    private Duration waitDurationInOpenState = Duration.ofSeconds(DEFAULT_WAIT_DURATION_IN_OPEN_STATE);
    // The default exception predicate counts all exceptions as failures.
    private Predicate<Throwable> recordFailurePredicate = DEFAULT_RECORD_FAILURE_PREDICATE;
    private boolean automaticTransitionFromOpenToHalfOpenEnabled = false;

    //......
}

Several parameters involved here are as follows:

  • FailureRateThreshold, the default is 50, that is, the failure rate threshold is 50%
  • RingBufferSizeInHalfOpenState, which sets the size of the ring buffer when the circuit breaker is in the HALF_OPEN state, stores the successful failure state of the request for a recent period of time, with a default of 10
  • RingBufferSizeInCLOSEDState, which sets the size of the ring buffer when the circuit breaker is in the Closed state, stores the successful failure state of the request for a recent period of time, with a default of 100.
  • WaitDurationInOpenState, which is used to specify the waiting time of circuit breaker from OPEN to HALF_OPEN state. The default is 60 seconds
  • RecordFailurePredicate is used to determine which exceptions should be counted as failures and included in breaker statistics. The default is Throwable type.
  • AutomaticTransitionFromOPENtOhalfOpenEnabled, when waitDurationInOpenState time passes, whether to automatically switch from Open to HALF_OPEN, the default is true

CircuitBreakerMetrics

resilience4j-circuitbreaker-0.13.0-sources.jar! /io/github/resilience4j/circuitbreaker/internal/CircuitBreakerMetrics.java

class CircuitBreakerMetrics implements CircuitBreaker.Metrics {

    private final int ringBufferSize;
    private final RingBitSet ringBitSet;
    private final LongAdder numberOfNotPermittedCalls;

    CircuitBreakerMetrics(int ringBufferSize) {
        this(ringBufferSize, null);
    }

    CircuitBreakerMetrics(int ringBufferSize, RingBitSet sourceSet) {
        this.ringBufferSize = ringBufferSize;
        if(sourceSet != null) {
            this.ringBitSet = new RingBitSet(this.ringBufferSize, sourceSet);
        }else{
            this.ringBitSet = new RingBitSet(this.ringBufferSize);
        }
        this.numberOfNotPermittedCalls = new LongAdder();
    }

    /**
     * Creates a new CircuitBreakerMetrics instance and copies the content of the current RingBitSet
     * into the new RingBitSet.
     *
     * @param targetRingBufferSize the ringBufferSize of the new CircuitBreakerMetrics instances
     * @return a CircuitBreakerMetrics
     */
    public CircuitBreakerMetrics copy(int targetRingBufferSize) {
        return new CircuitBreakerMetrics(targetRingBufferSize, this.ringBitSet);
    }

    /**
     * Records a failed call and returns the current failure rate in percentage.
     *
     * @return the current failure rate  in percentage.
     */
    float onError() {
        int currentNumberOfFailedCalls = ringBitSet.setNextBit(true);
        return getFailureRate(currentNumberOfFailedCalls);
    }

    /**
     * Records a successful call and returns the current failure rate in percentage.
     *
     * @return the current failure rate in percentage.
     */
    float onSuccess() {
        int currentNumberOfFailedCalls = ringBitSet.setNextBit(false);
        return getFailureRate(currentNumberOfFailedCalls);
    }

    /**
     * Records a call which was not permitted, because the CircuitBreaker state is OPEN.
     */
    void onCallNotPermitted() {
        numberOfNotPermittedCalls.increment();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public float getFailureRate() {
        return getFailureRate(getNumberOfFailedCalls());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getMaxNumberOfBufferedCalls() {
        return ringBufferSize;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getNumberOfSuccessfulCalls() {
        return getNumberOfBufferedCalls() - getNumberOfFailedCalls();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getNumberOfBufferedCalls() {
        return this.ringBitSet.length();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long getNumberOfNotPermittedCalls() {
        return this.numberOfNotPermittedCalls.sum();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getNumberOfFailedCalls() {
        return this.ringBitSet.cardinality();
    }

    private float getFailureRate(int numberOfFailedCalls) {
        if (getNumberOfBufferedCalls() < ringBufferSize) {
            return -1.0f;
        }
        return numberOfFailedCalls * 100.0f / ringBufferSize;
    }
}
  • The ringBitSet.setNextBit method returns the number set to true in this ringBitSet, here is numberOfFailedCalls
  • GetFailureRate judges first that if the number in ringBitSet does not reach the size specified by ringBufferSize, then -1.0f will be returned and the failure rate will not be calculated.
  • The failure rate is calculated as numberoffailedcalls * 100.0f/ringbuffer size

RingBitSet

resilience4j-circuitbreaker-0.13.0-sources.jar! /io/github/resilience4j/circuitbreaker/internal/RingBitSet.java

/**
 * A ring bit set which stores bits up to a maximum size of bits.
 */
class RingBitSet {

    private final int size;
    private final BitSetMod bitSet;

    private boolean notFull;
    private int index = -1;

    private volatile int length;
    private volatile int cardinality = 0;

    //......

    /**
     * Sets the bit at the next index to the specified value.
     *
     * @param value a boolean value to set
     * @return the number of bits set to {@code true}
     */
    public synchronized int setNextBit(boolean value) {
        increaseLength();
        index = (index + 1) % size;

        int previous = bitSet.set(index, value);
        int current = value ? 1 : 0;
        cardinality = cardinality - previous + current;
        return cardinality;
    }

    private void increaseLength() {
        if (notFull) {
            int nextLength = length + 1;
            if (nextLength < size) {
                length = nextLength;
            } else {
                length = size;
                notFull = false;
            }
        }
    }
}
  • Focus on the setNextBit method, where cardinality is the number set to true in ringbuffer.
  • The increaselength method increments if the length does not exceed the size, resets to the size if it exceeds the size, that is, the maximum size, and then marks whether it is full or not with notFull.

HalfOpenState

resilience4j-circuitbreaker-0.13.0-sources.jar! /io/github/resilience4j/circuitbreaker/internal/HalfOpenState.java

final class HalfOpenState extends CircuitBreakerState {

    private CircuitBreakerMetrics circuitBreakerMetrics;
    private final float failureRateThreshold;

    HalfOpenState(CircuitBreakerStateMachine stateMachine) {
        super(stateMachine);
        CircuitBreakerConfig circuitBreakerConfig = stateMachine.getCircuitBreakerConfig();
        this.circuitBreakerMetrics = new CircuitBreakerMetrics(
                circuitBreakerConfig.getRingBufferSizeInHalfOpenState());
        this.failureRateThreshold = stateMachine.getCircuitBreakerConfig().getFailureRateThreshold();
    }

    /**
     * Returns always true, because the CircuitBreaker is half open.
     *
     * @return always true, because the CircuitBreaker is half open.
     */
    @Override
    boolean isCallPermitted() {
        return true;
    }

    @Override
    void onError(Throwable throwable) {
        // CircuitBreakerMetrics is thread-safe
        checkFailureRate(circuitBreakerMetrics.onError());
    }

    @Override
    void onSuccess() {
        // CircuitBreakerMetrics is thread-safe
        checkFailureRate(circuitBreakerMetrics.onSuccess());
    }

    /**
     * Checks if the current failure rate is above or below the threshold.
     * If the failure rate is above the threshold, transition the state machine to OPEN state.
     * If the failure rate is below the threshold, transition the state machine to CLOSED state.
     *
     * @param currentFailureRate the current failure rate
     */
    private void checkFailureRate(float currentFailureRate) {
        if(currentFailureRate != -1){
            if(currentFailureRate >= failureRateThreshold) {
                stateMachine.transitionToOpenState();
            }else{
                stateMachine.transitionToClosedState();
            }
        }
    }

    /**
     * Get the state of the CircuitBreaker
     */
    @Override
    CircuitBreaker.State getState() {
        return CircuitBreaker.State.HALF_OPEN;
    }

    @Override
    CircuitBreakerMetrics getMetrics() {
        return circuitBreakerMetrics;
    }
}
  • The ringBufferSize here is getringbufferferrsizeinhalfopenstate.
  • Here, both onError and onSuccess will trigger checkFailureRate to check whether the failure rate reaches the threshold.
  • When checkFailureRate judges, it first excludes -1.0f, then switches the state to OPEN if it reaches the threshold, and switches to CLOSED if it does not reach the threshold.

ClosedState

resilience4j-circuitbreaker-0.13.0-sources.jar! /io/github/resilience4j/circuitbreaker/internal/ClosedState.java

final class ClosedState extends CircuitBreakerState {

    private final CircuitBreakerMetrics circuitBreakerMetrics;
    private final float failureRateThreshold;

    ClosedState(CircuitBreakerStateMachine stateMachine) {
        this(stateMachine, null);
    }

    ClosedState(CircuitBreakerStateMachine stateMachine, CircuitBreakerMetrics circuitBreakerMetrics) {
        super(stateMachine);
        CircuitBreakerConfig circuitBreakerConfig = stateMachine.getCircuitBreakerConfig();
        if(circuitBreakerMetrics == null){
            this.circuitBreakerMetrics = new CircuitBreakerMetrics(
                circuitBreakerConfig.getRingBufferSizeInClosedState());
        }else{
            this.circuitBreakerMetrics = circuitBreakerMetrics.copy(circuitBreakerConfig.getRingBufferSizeInClosedState());
        }
        this.failureRateThreshold = stateMachine.getCircuitBreakerConfig().getFailureRateThreshold();
    }

    /**
     * Returns always true, because the CircuitBreaker is closed.
     *
     * @return always true, because the CircuitBreaker is closed.
     */
    @Override
    boolean isCallPermitted() {
        return true;
    }

    @Override
    void onError(Throwable throwable) {
        // CircuitBreakerMetrics is thread-safe
        checkFailureRate(circuitBreakerMetrics.onError());
    }

    @Override
    void onSuccess() {
        // CircuitBreakerMetrics is thread-safe
        checkFailureRate(circuitBreakerMetrics.onSuccess());
    }

    /**
     * Checks if the current failure rate is above the threshold.
     * If the failure rate is above the threshold, transitions the state machine to OPEN state.
     *
     * @param currentFailureRate the current failure rate
     */
    private void checkFailureRate(float currentFailureRate) {
        if (currentFailureRate >= failureRateThreshold) {
            // Transition the state machine to OPEN state, because the failure rate is above the threshold
            stateMachine.transitionToOpenState();
        }
    }

    /**
     * Get the state of the CircuitBreaker
     */
    @Override
    CircuitBreaker.State getState() {
        return CircuitBreaker.State.CLOSED;
    }
    /**
     *
     * Get metrics of the CircuitBreaker
     */
    @Override
    CircuitBreakerMetrics getMetrics() {
        return circuitBreakerMetrics;
    }
}
  • The ringBufferSize here is getRingBufferSizeInClosedState.
  • Here, both onError and onSuccess will trigger checkFailureRate to check whether the failure rate reaches the threshold.
  • CheckFailureRate judges that if it is greater than the set threshold, the state will be changed to OPEN.

Summary

  • The CircuitBreakerConfig of resilience4j mainly sets failureRateThreshold, ringBufferSizeInHalfOpenState, ringBufferSizeInClosedState, waitDurationInOpenState, RecordFailurePredicate, AutomaticTransitionFromOpentOhalfOpenEnabled.
  • RingBufferSizeInHalfOpenState and ringBufferSizeInClosedState set the size of ringbuffer and also affect the calculation of failureRate, that is, if the length in ringbuffer is less than this size, the failure rate will not be calculated.
  • Length in ringbuffer is incremented, but does not exceed the specified size, with the maximum being the specified size.

doc