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

com.github.mtakaki.dropwizard.circuitbreaker.CircuitBreakerManager Maven / Gradle / Ivy

package com.github.mtakaki.dropwizard.circuitbreaker;

import java.util.concurrent.ConcurrentHashMap;

import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.github.mtakaki.dropwizard.circuitbreaker.jersey.CircuitBreaker;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * Following circuit breaker design pattern, this class will wrap a given code
 * block into a {@link Meter}. Once that meter reaches a given threshold, our
 * circuit gets open and it will consider it unavailable until the rate goes
 * below the given threshold.
 *
 * This class can be used stand-alone, without the usage of
 * {@link CircuitBreaker} annotation in jersey resources.
 *
 * @author mtakaki
 *
 */
public class CircuitBreakerManager {
    private final ConcurrentHashMap circuitBreakerMap;
    private final MetricRegistry metricRegistry;
    @Getter
    private final double defaultThreshold;
    @Getter
    private final RateType rateType;

    /**
     * Holds the {@link Meter} and the threshold. The threshold can either be a
     * custom threshold or the default one.
     */
    @AllArgsConstructor
    @Getter
    private static class MeterThreshold {
        private final Meter meter;
        private final double threshold;
    }

    /**
     * An operation block that will be wrapped by {@link CircuitBreakerManager}
     * and will catch any exception. In case of an exception, it will increase
     * the meter and, eventually, can cause the circuit to open.
     */
    @FunctionalInterface
    public static interface Operation {
        public void accept(final Meter meter) throws OperationException;
    }

    /**
     * Build a new instance of {@link CircuitBreakerManager}. This instance will
     * be thread-safe, so it should be shared among different threads.
     *
     * @param defaultThreshold
     *            The threshold that will determine if a circuit should be
     *            opened or not. This threshold unit is requests per second.
     * @param metricRegistry
     *            The {@link MetricRegistry} that will be used to build, and
     *            register, our {@link Meter}.
     * @param rateType
     *            The rate unit used to determining if the circuit is opened or
     *            not.
     */
    public CircuitBreakerManager(final MetricRegistry metricRegistry, final double defaultThreshold,
            final RateType rateType) {
        this.circuitBreakerMap = new ConcurrentHashMap<>();
        this.defaultThreshold = defaultThreshold;
        this.metricRegistry = metricRegistry;
        this.rateType = rateType;
    }

    /**
     * Will retrieve, or build if it doesn't exist yet, the {@link Meter} that
     * backs the circuit with the given name. If the circuit breaker doesn't
     * exist yet, it will use the default threshold. If you want to use a custom
     * threshold, use the other {@code getMeter()} method.
     *
     * @param name
     *            The circuit name.
     * @return The meter that belongs to the circuit with the given name.
     */
    public Meter getMeter(final String name) {
        return this.getMeter(name, this.defaultThreshold);
    }

    /**
     * Will retrieve, or build if it doesn't exist yet, the {@link Meter} that
     * backs the circuit with the given name.
     *
     * @param name
     *            The circuit name.
     * @param threshold
     *            The circuit breaker custom threshold, so it won't use the
     *            default one.
     * @return The meter that belongs to the circuit with the given name.
     */
    public Meter getMeter(final String name, final double threshold) {
        MeterThreshold meterThreshold = this.circuitBreakerMap.get(name);

        if (meterThreshold == null) {
            final Meter meter = this.metricRegistry.meter(name);
            meterThreshold = new MeterThreshold(meter, threshold);
            this.circuitBreakerMap.put(name, meterThreshold);
        }

        return meterThreshold.getMeter();
    }

    /**
     * Executes the given {@link Operation} and if it throw any exception the
     * meter will be increased, which can cause the circuit to be opened.
     *
     * @param name
     *            The circuit name.
     * @param codeBlock
     *            The code block that will be executed.
     * @throws OperationException
     *             The exception thrown from the given code block.
     */
    public void wrapCodeBlock(final String name, final Operation codeBlock)
            throws OperationException {
        final Meter exceptionMeter = this.getMeter(name);

        try {
            codeBlock.accept(exceptionMeter);
        } catch (final Exception e) {
            exceptionMeter.mark();
            throw e;
        }
    }

    /**
     * Verifies if the circuit breaker is not opened before trying to execute
     * the given code block. If it's not opened it will execute the given
     * {@link Operation} and if it throw any exception the meter will be
     * increased, which can cause the circuit to be opened.
     *
     * @param name
     *            The circuit name.
     * @param codeBlock
     *            The code block that will be executed.
     * @throws CircuitBreakerOpenedException
     *             Thrown if the circuit breaker is opened and the block can't
     *             be executed.
     * @throws OperationException
     *             The exception thrown from the given code block.
     */
    public void wrapCodeBlockWithCircuitBreaker(final String name, final Operation codeBlock)
            throws CircuitBreakerOpenedException, OperationException {
        if (this.isCircuitOpen(name)) {
            throw new CircuitBreakerOpenedException(name);
        }

        this.wrapCodeBlock(name, codeBlock);
    }

    /**
     * Verifies if the circuit is opened for a given circuit name. It will use
     * the given {@link RateType} to determine if the circuit is opened or not.
     * If the meter has not been previously created, it will create it using the
     * default threshold. To make sure it uses the correct threshold, call
     * {@code getMeter()} before-hand.
     *
     * @param name
     *            The circuit name.
     * @return {@code true} if the circuit is open, which means the service is
     *         unavailable, or {@code false} if the circuit is closed, meaning
     *         the service is healthy again.
     */
    public boolean isCircuitOpen(final String name) {
        final Meter exceptionMeter = this.getMeter(name);

        switch (this.rateType) {
        case MEAN:
            return exceptionMeter.getMeanRate() >= this.defaultThreshold;
        case ONE_MINUTE:
            return exceptionMeter.getOneMinuteRate() >= this.defaultThreshold;
        case FIVE_MINUTES:
            return exceptionMeter.getFiveMinuteRate() >= this.defaultThreshold;
        case FIFTEEN_MINUTES:
            return exceptionMeter.getFifteenMinuteRate() >= this.defaultThreshold;
        default:
            return false;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy