org.zkybase.kite.circuitbreaker.CircuitBreakerTemplate.java Source code

Java tutorial

Introduction

Here is the source code for org.zkybase.kite.circuitbreaker.CircuitBreakerTemplate.java

Source

/*
 * Copyright (c) 2010 the original author or authors.
 *
 * 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 org.zkybase.kite.circuitbreaker;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.util.Assert;

/**
 * <p>
 * Template for circuit breakers, which are components designed to protect
 * clients from broken services by preventing faults from propagating across
 * integration points.
 * </p>
 * <p>
 * For example, suppose that we have a web application that calls a web service.
 * If the web service is having a problem (e.g., severe latency or perhaps
 * complete unavailability), we don't want this to create problems for the app.
 * Instead we want to isolate the problem.
 * </p>
 * <p>
 * The circuit breaker allows us to do just this. We associate a circuit breaker
 * with each integration point, and calls from the client to the service are
 * mediated by the breaker. Under normal circumstances the breaker is in the
 * closed state, and calls pass through to the service. If however there is a
 * problem, then the breaker goes into the open state for some period of time.
 * While the breaker is open, all attempts to call the service fail with a
 * {@link CircuitOpenException}. Once the problem is resolved, the breaker
 * returns to the closed state and normal operations resume.
 * </p>
 * <p>
 * The circuit breaker pattern is described in detail in Michael Nygard's book,
 * <a href="http://www.pragprog.com/titles/mnee/release-it">Release It!</a>
 * (Pragmatic).
 * </p>
 * 
 * @author Willie Wheeler
 * @since 1.0
 */
@ManagedResource
public class CircuitBreakerTemplate implements BeanNameAware {
    public enum State {
        CLOSED, OPEN, HALF_OPEN
    };

    private static final long NO_SCHEDULED_RETRY = Long.MAX_VALUE;
    private static Logger log = LoggerFactory.getLogger(CircuitBreakerTemplate.class);

    // Configuration
    private String beanName;
    private int exceptionThreshold = 5;
    private long timeout = 30000L;
    private List<Class<? extends Exception>> handledExceptions = new ArrayList<Class<? extends Exception>>();

    // Volatile state
    private volatile State state = State.CLOSED;
    private final AtomicInteger exceptionCount = new AtomicInteger();
    private volatile long retryTime = NO_SCHEDULED_RETRY;

    public CircuitBreakerTemplate() {
        handledExceptions.add(Exception.class);
    }

    @ManagedAttribute(description = "Breaker name")
    public String getBeanName() {
        return beanName;
    }

    /**
     * <p>
     * Required by {@link org.springframework.beans.factory.BeanNameAware}. We
     * use this to log state transitions.
     * </p>
     * 
     * @param beanName
     *            bean's name (or ID)
     */
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    /**
     * <p>
     * Returns the exception threshold for this breaker. This is the number of
     * exceptions causing the breaker to trip.
     * </p>
     * 
     * @return exception threshold for this breaker
     */
    @ManagedAttribute(description = "Breaker trips when threshold is reached")
    public int getExceptionThreshold() {
        return exceptionThreshold;
    }

    /**
     * <p>
     * Sets the exception threshold for this breaker. Once the threshold is
     * reached, the breaker trips.
     * </p>
     * <p>
     * The default exception threshold is 5.
     * </p>
     * 
     * @param threshold
     *            number of exceptions causing the breaker to trip
     * @throws IllegalArgumentException
     *             if threshold &lt; 1
     */
    @ManagedAttribute(description = "Breaker trips when threshold is reached", defaultValue = "5")
    public void setExceptionThreshold(int threshold) {
        Assert.isTrue(threshold >= 1, "threshold must be >= 1");
        this.exceptionThreshold = threshold;
    }

    /**
     * <p>
     * Returns the open state timeout in milliseconds. After the timeout
     * expires, the next call causes the breaker to go into the half-open state.
     * </p>
     * 
     * @return open state timeout in milliseconds
     */
    @ManagedAttribute(description = "Delay in ms before open breaker goes half-open")
    public long getTimeout() {
        return timeout;
    }

    /**
     * <p>
     * Sets the open state timeout in milliseconds.
     * </p>
     * 
     * @param timeout
     *            open state timeout in milliseconds
     * @throws IllegalArgumentException
     *             if timeout &lt; 0
     */
    @ManagedAttribute(description = "Delay in ms before open breaker goes half-open", defaultValue = "30000")
    public void setTimeout(long timeout) {
        Assert.isTrue(timeout >= 0L, "timeout must be >= 0");
        this.timeout = timeout;
    }

    public List<Class<? extends Exception>> getHandledExceptions() {
        return handledExceptions;
    }

    public void setHandledExceptions(List<Class<? extends Exception>> exceptions) {
        Assert.notNull(exceptions, "handledExceptions can't be null");
        this.handledExceptions = exceptions;
    }

    /**
     * <p>
     * Returns the breaker's state, which is {@link State#CLOSED},
     * {@link State#OPEN} or {@link State#HALF_OPEN}.
     * </p>
     * 
     * @return breaker's state
     */
    @ManagedAttribute(description = "Breaker state (closed, open, half-open)")
    public State getState() {
        if (state == State.OPEN) {
            if (System.currentTimeMillis() >= retryTime) {
                log.info("Setting circuit breaker half-open: {}", beanName);
                this.state = State.HALF_OPEN;
            }
        }
        return state;
    }

    // For testing
    void setState(State state) {
        this.state = state;
    }

    @ManagedAttribute(description = "Number of exceptions since last reset")
    public int getExceptionCount() {
        return exceptionCount.get();
    }

    @ManagedAttribute(description = "Breaker will retry circuit at or after this time")
    public long getRetryTime() {
        return retryTime;
    }

    /**
     * <p>
     * Restricted visibility method to support unit tests.
     * </p>
     * 
     * @param exceptionCount
     */
    void setExceptionCount(int exceptionCount) {
        this.exceptionCount.set(exceptionCount);
    }

    /**
     * <p>
     * Forces the breaker into the closed state, which is the default state. The
     * closed state allows calls to pass through.
     * </p>
     */
    @ManagedOperation(description = "Resets the breaker")
    public void reset() {
        log.info("Resetting circuit breaker: {}", beanName);
        this.state = State.CLOSED;
        this.exceptionCount.set(0);
    }

    /**
     * <p>
     * Forces the breaker into the open state. The open state prevents calls
     * from passing through.
     * </p>
     */
    @ManagedOperation(description = "Trips the breaker, auto-resetting after timeout")
    public void trip() {
        trip(true);
    }

    @ManagedOperation(description = "Trips the breaker without auto-resetting")
    public void tripWithoutAutoReset() {
        trip(false);
    }

    private void trip(boolean autoReset) {
        log.warn("Tripping breaker {}, autoReset={}", beanName, autoReset);
        this.state = State.OPEN;

        // FIXME Don't want races to mosh explicit tripWithoutAutoReset() requests...
        this.retryTime = (autoReset ? System.currentTimeMillis() + timeout : NO_SCHEDULED_RETRY);
    }

    /**
     * <p>
     * Executes the specified action inside the circuit breaker.
     * </p>
     * 
     * @param <T>
     *            action return type
     * @param action
     *            action to execute
     * @return result of the action
     * @throws CircuitOpenException
     *             if the breaker is in the open state
     * @throws Exception
     *             exception thrown by the action, if any
     */
    public <T> T execute(CircuitBreakerCallback<T> action) throws Exception {
        final State currState = getState();
        switch (currState) {

        case CLOSED:
            try {
                T value = action.doInCircuitBreaker();
                this.exceptionCount.set(0);
                return value;
            } catch (Exception e) {
                if (isHandledException(e.getClass()) && exceptionCount.incrementAndGet() >= exceptionThreshold) {
                    trip();
                }

                // In any event, throw the exception.
                throw e;
            }

        case OPEN:
            throw new CircuitOpenException();

        case HALF_OPEN:
            try {
                T value = action.doInCircuitBreaker();
                reset();
                return value;
            } catch (Exception e) {
                if (isHandledException(e.getClass())) {
                    trip();
                }
                throw e;
            }

        default:
            // This shouldn't happen...
            throw new IllegalStateException("Unknown state: " + currState);
        }
    }

    // Check the exception against the list of handled exceptions.
    private boolean isHandledException(Class<? extends Exception> exceptionClass) {
        for (Class<? extends Exception> handledExceptionClass : handledExceptions) {
            if (handledExceptionClass.isAssignableFrom(exceptionClass)) {
                return true;
            }
        }
        return false;
    }
}