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

org.apache.camel.impl.ThrottlingExceptionRoutePolicy Maven / Gradle / Ivy

There is a newer version: 4.6.0
Show newest version
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.camel.impl;

import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.Exchange;
import org.apache.camel.Route;
import org.apache.camel.processor.loadbalancer.CircuitBreakerLoadBalancer;
import org.apache.camel.spi.RoutePolicy;
import org.apache.camel.support.RoutePolicySupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Modeled after the {@link CircuitBreakerLoadBalancer} and {@link ThrottlingInflightRoutePolicy}
 * this {@link RoutePolicy} will stop consuming from an endpoint based on the type of exceptions that are
 * thrown and the threshold setting. 
 * 
 * the scenario: if a route cannot process data from an endpoint due to problems with resources used by the route
 * (ie database down) then it will stop consuming new messages from the endpoint by stopping the consumer. 
 * The implementation is comparable to the Circuit Breaker pattern. After a set amount of time, it will move 
 * to a half open state and attempt to determine if the consumer can be started.
 * There are two ways to determine if a route can be closed after being opened
 * (1) start the consumer and check the failure threshold
 * (2) call the {@link ThrottlingExceptionHalfOpenHandler} 
 * The second option allows a custom check to be performed without having to take on the possibility of
 * multiple messages from the endpoint. The idea is that a handler could run a simple test (ie select 1 from dual)
 * to determine if the processes that cause the route to be open are now available  
 */
public class ThrottlingExceptionRoutePolicy extends RoutePolicySupport implements CamelContextAware {
    private static final Logger LOG = LoggerFactory.getLogger(ThrottlingExceptionRoutePolicy.class);
    
    private static final int STATE_CLOSED = 0;
    private static final int STATE_HALF_OPEN = 1;
    private static final int STATE_OPEN = 2;
    
    private CamelContext camelContext;
    private final Lock lock = new ReentrantLock();
    
    // configuration
    private int failureThreshold;
    private long failureWindow;
    private long halfOpenAfter;
    private final List> throttledExceptions;
    
    // handler for half open circuit
    // can be used instead of resuming route
    // to check on resources
    private ThrottlingExceptionHalfOpenHandler halfOpenHandler;

    // stateful information
    private final AtomicInteger failures = new AtomicInteger();
    private final AtomicInteger state = new AtomicInteger(STATE_CLOSED);
    private volatile Timer halfOpenTimer;
    private volatile long lastFailure;
    private volatile long openedAt;
    
    public ThrottlingExceptionRoutePolicy(int threshold, long failureWindow, long halfOpenAfter, List> handledExceptions) {
        this.throttledExceptions = handledExceptions;
        this.failureWindow = failureWindow;
        this.halfOpenAfter = halfOpenAfter;
        this.failureThreshold = threshold;
    }
    
    @Override
    public void setCamelContext(CamelContext camelContext) {
        this.camelContext = camelContext;
    }

    @Override
    public CamelContext getCamelContext() {
        return camelContext;
    }

    @Override
    public void onInit(Route route) {
        LOG.debug("Initializing ThrottlingExceptionRoutePolicy route policy...");
        logState();
    }
    
    @Override
    public void onExchangeDone(Route route, Exchange exchange) {
        if (hasFailed(exchange)) {
            // record the failure
            failures.incrementAndGet();
            lastFailure = System.currentTimeMillis();
        } 
        
        // check for state change
        calculateState(route);
    }
    
    /**
     * uses similar approach as {@link CircuitBreakerLoadBalancer}
     * if the exchange has an exception that we are watching 
     * then we count that as a failure otherwise we ignore it
     */
    private boolean hasFailed(Exchange exchange) {
        if (exchange == null) {
            return false;
        }

        boolean answer = false;

        if (exchange.getException() != null) {
            if (throttledExceptions == null || throttledExceptions.isEmpty()) {
                // if no exceptions defined then always fail 
                // (ie) assume we throttle on all exceptions
                answer = true;
            } else {
                for (Class exception : throttledExceptions) {
                    // will look in exception hierarchy
                    if (exchange.getException(exception) != null) {
                        answer = true;
                        break;
                    }
                }
            }
        }

        if (LOG.isDebugEnabled()) {
            String exceptionName = exchange.getException() == null ? "none" : exchange.getException().getClass().getSimpleName();
            LOG.debug("hasFailed ({}) with Throttled Exception: {} for exchangeId: {}", answer, exceptionName, exchange.getExchangeId());
        }
        return answer;
    }

    private void calculateState(Route route) {
        
        // have we reached the failure limit?
        boolean failureLimitReached = isThresholdExceeded();
        
        if (state.get() == STATE_CLOSED) {
            if (failureLimitReached) {
                LOG.debug("Opening circuit...");
                openCircuit(route);
            }
        } else if (state.get() == STATE_HALF_OPEN) {
            if (failureLimitReached) {
                LOG.debug("Opening circuit...");
                openCircuit(route);
            } else {
                LOG.debug("Closing circuit...");
                closeCircuit(route);
            }
        } else if (state.get() == STATE_OPEN) {
            long elapsedTimeSinceOpened = System.currentTimeMillis() - openedAt;
            if (halfOpenAfter <= elapsedTimeSinceOpened) {
                LOG.debug("Checking an open circuit...");
                if (halfOpenHandler != null) {
                    if (halfOpenHandler.isReadyToBeClosed()) {
                        LOG.debug("Closing circuit...");
                        closeCircuit(route);
                    } else {
                        LOG.debug("Opening circuit...");
                        openCircuit(route);
                    }
                } else {
                    LOG.debug("Half opening circuit...");
                    halfOpenCircuit(route);                    
                }
            } 
        }
        
    }
    
    protected boolean isThresholdExceeded() {
        boolean output = false;
        logState();
        // failures exceed the threshold 
        // AND the last of those failures occurred within window
        if ((failures.get() >= failureThreshold) && (lastFailure >= System.currentTimeMillis() - failureWindow)) {
            output = true;
        }
        
        return output;
    }
        
    protected void openCircuit(Route route) {
        try {
            lock.lock();
            stopConsumer(route.getConsumer());
            state.set(STATE_OPEN);
            openedAt = System.currentTimeMillis();
            halfOpenTimer = new Timer();
            halfOpenTimer.schedule(new HalfOpenTask(route), halfOpenAfter);
            logState();
        } catch (Exception e) {
            handleException(e);
        } finally {
            lock.unlock();
        }
    }

    protected void halfOpenCircuit(Route route) {
        try {
            lock.lock();
            startConsumer(route.getConsumer());
            state.set(STATE_HALF_OPEN);
            logState();
        } catch (Exception e) {
            handleException(e);
        } finally {
            lock.unlock();
        }
    }
    
    protected void closeCircuit(Route route) {
        try {
            lock.lock();
            startConsumer(route.getConsumer());
            failures.set(0);
            lastFailure = 0;
            openedAt = 0;
            state.set(STATE_CLOSED);
            logState();
        } catch (Exception e) {
            handleException(e);
        } finally {
            lock.unlock();
        }
    }
    
    private void logState() {
        if (LOG.isDebugEnabled()) {
            LOG.debug(dumpState());
        }
    }
    
    public String dumpState() {
        int num = state.get();
        String routeState = stateAsString(num);
        if (failures.get() > 0) {
            return String.format("State %s, failures %d, last failure %d ms ago", routeState, failures.get(), System.currentTimeMillis() - lastFailure);
        } else {
            return String.format("State %s, failures %d", routeState, failures.get());
        }
    }
    
    private static String stateAsString(int num) {
        if (num == STATE_CLOSED) {
            return "closed";
        } else if (num == STATE_HALF_OPEN) {
            return "half opened";
        } else {
            return "opened";
        }
    }
    
    class HalfOpenTask extends TimerTask {
        private final Route route;
        
        HalfOpenTask(Route route) {
            this.route = route;
        }
        
        @Override
        public void run() {
            calculateState(route);
            halfOpenTimer.cancel();
        }
    }
    
    public ThrottlingExceptionHalfOpenHandler getHalfOpenHandler() {
        return halfOpenHandler;
    }

    public void setHalfOpenHandler(ThrottlingExceptionHalfOpenHandler halfOpenHandler) {
        this.halfOpenHandler = halfOpenHandler;
    }

    public int getFailureThreshold() {
        return failureThreshold;
    }

    public void setFailureThreshold(int failureThreshold) {
        this.failureThreshold = failureThreshold;
    }

    public long getFailureWindow() {
        return failureWindow;
    }

    public void setFailureWindow(long failureWindow) {
        this.failureWindow = failureWindow;
    }

    public long getHalfOpenAfter() {
        return halfOpenAfter;
    }

    public void setHalfOpenAfter(long halfOpenAfter) {
        this.halfOpenAfter = halfOpenAfter;
    }

    public int getFailures() {
        return failures.get();
    }

    public long getLastFailure() {
        return lastFailure;
    }

    public long getOpenedAt() {
        return openedAt;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy