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

org.apache.camel.processor.loadbalancer.FailOverLoadBalancer 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.processor.loadbalancer;

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

import org.apache.camel.AsyncCallback;
import org.apache.camel.AsyncProcessor;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.Traceable;
import org.apache.camel.util.AsyncProcessorConverterHelper;
import org.apache.camel.util.ExchangeHelper;
import org.apache.camel.util.ObjectHelper;

/**
 * This FailOverLoadBalancer will failover to use next processor when an exception occurred
 * 

* This implementation mirrors the logic from the {@link org.apache.camel.processor.Pipeline} in the async variation * as the failover load balancer is a specialized pipeline. So the trick is to keep doing the same as the * pipeline to ensure it works the same and the async routing engine is flawless. */ public class FailOverLoadBalancer extends LoadBalancerSupport implements Traceable, CamelContextAware { private final List> exceptions; private CamelContext camelContext; private boolean roundRobin; private boolean sticky; private int maximumFailoverAttempts = -1; // stateful statistics private final AtomicInteger counter = new AtomicInteger(-1); private final AtomicInteger lastGoodIndex = new AtomicInteger(-1); private final ExceptionFailureStatistics statistics = new ExceptionFailureStatistics(); public FailOverLoadBalancer() { this.exceptions = null; } public FailOverLoadBalancer(List> exceptions) { this.exceptions = exceptions; // validate its all exception types for (Class type : exceptions) { if (!ObjectHelper.isAssignableFrom(Throwable.class, type)) { throw new IllegalArgumentException("Class is not an instance of Throwable: " + type); } } statistics.init(exceptions); } @Override public CamelContext getCamelContext() { return camelContext; } @Override public void setCamelContext(CamelContext camelContext) { this.camelContext = camelContext; } public int getLastGoodIndex() { return lastGoodIndex.get(); } public List> getExceptions() { return exceptions; } public boolean isRoundRobin() { return roundRobin; } public void setRoundRobin(boolean roundRobin) { this.roundRobin = roundRobin; } public boolean isSticky() { return sticky; } public void setSticky(boolean sticky) { this.sticky = sticky; } public int getMaximumFailoverAttempts() { return maximumFailoverAttempts; } public void setMaximumFailoverAttempts(int maximumFailoverAttempts) { this.maximumFailoverAttempts = maximumFailoverAttempts; } /** * Should the given failed Exchange failover? * * @param exchange the exchange that failed * @return true to failover */ protected boolean shouldFailOver(Exchange exchange) { if (exchange == null) { return false; } boolean answer = false; if (exchange.getException() != null) { if (exceptions == null || exceptions.isEmpty()) { // always failover if no exceptions defined answer = true; } else { for (Class exception : exceptions) { // will look in exception hierarchy if (exchange.getException(exception) != null) { answer = true; break; } } } if (answer) { // record the failure in the statistics statistics.onHandledFailure(exchange.getException()); } } log.trace("Should failover: {} for exchangeId: {}", answer, exchange.getExchangeId()); return answer; } @Override public boolean isRunAllowed() { // determine if we can still run, or the camel context is forcing a shutdown boolean forceShutdown = camelContext.getShutdownStrategy().forceShutdown(this); if (forceShutdown) { log.trace("Run not allowed as ShutdownStrategy is forcing shutting down"); } return !forceShutdown && super.isRunAllowed(); } public boolean process(final Exchange exchange, final AsyncCallback callback) { final List processors = getProcessors(); final AtomicInteger index = new AtomicInteger(); final AtomicInteger attempts = new AtomicInteger(); boolean first = true; // use a copy of the original exchange before failover to avoid populating side effects // directly into the original exchange Exchange copy = null; // get the next processor if (isSticky()) { int idx = lastGoodIndex.get(); if (idx == -1) { idx = 0; } index.set(idx); } else if (isRoundRobin()) { if (counter.incrementAndGet() >= processors.size()) { counter.set(0); } index.set(counter.get()); } log.trace("Failover starting with endpoint index {}", index); while (first || shouldFailOver(copy)) { // can we still run if (!isRunAllowed()) { log.trace("Run not allowed, will reject executing exchange: {}", exchange); if (exchange.getException() == null) { exchange.setException(new RejectedExecutionException()); } // we cannot process so invoke callback callback.done(true); return true; } if (!first) { attempts.incrementAndGet(); // are we exhausted by attempts? if (maximumFailoverAttempts > -1 && attempts.get() > maximumFailoverAttempts) { log.debug("Breaking out of failover after {} failover attempts", attempts); break; } index.incrementAndGet(); counter.incrementAndGet(); } else { // flip first switch first = false; } if (index.get() >= processors.size()) { // out of bounds if (isRoundRobin()) { log.trace("Failover is round robin enabled and therefore starting from the first endpoint"); index.set(0); counter.set(0); } else { // no more processors to try log.trace("Breaking out of failover as we reached the end of endpoints to use for failover"); break; } } // try again but copy original exchange before we failover copy = prepareExchangeForFailover(exchange); Processor processor = processors.get(index.get()); // process the exchange boolean sync = processExchange(processor, exchange, copy, attempts, index, callback, processors); // continue as long its being processed synchronously if (!sync) { log.trace("Processing exchangeId: {} is continued being processed asynchronously", exchange.getExchangeId()); // the remainder of the failover will be completed async // so we break out now, then the callback will be invoked which then continue routing from where we left here return false; } log.trace("Processing exchangeId: {} is continued being processed synchronously", exchange.getExchangeId()); } // remember last good index lastGoodIndex.set(index.get()); // and copy the current result to original so it will contain this result of this eip if (copy != null) { ExchangeHelper.copyResults(exchange, copy); } log.debug("Failover complete for exchangeId: {} >>> {}", exchange.getExchangeId(), exchange); callback.done(true); return true; } /** * Prepares the exchange for failover * * @param exchange the exchange * @return a copy of the exchange to use for failover */ protected Exchange prepareExchangeForFailover(Exchange exchange) { // use a copy of the exchange to avoid side effects on the original exchange return ExchangeHelper.createCopy(exchange, true); } private boolean processExchange(Processor processor, Exchange exchange, Exchange copy, AtomicInteger attempts, AtomicInteger index, AsyncCallback callback, List processors) { if (processor == null) { throw new IllegalStateException("No processors could be chosen to process " + copy); } log.debug("Processing failover at attempt {} for {}", attempts, copy); AsyncProcessor albp = AsyncProcessorConverterHelper.convert(processor); return albp.process(copy, new FailOverAsyncCallback(exchange, copy, attempts, index, callback, processors)); } /** * Failover logic to be executed asynchronously if one of the failover endpoints * is a real {@link AsyncProcessor}. */ private final class FailOverAsyncCallback implements AsyncCallback { private final Exchange exchange; private Exchange copy; private final AtomicInteger attempts; private final AtomicInteger index; private final AsyncCallback callback; private final List processors; private FailOverAsyncCallback(Exchange exchange, Exchange copy, AtomicInteger attempts, AtomicInteger index, AsyncCallback callback, List processors) { this.exchange = exchange; this.copy = copy; this.attempts = attempts; this.index = index; this.callback = callback; this.processors = processors; } public void done(boolean doneSync) { // we only have to handle async completion of the pipeline if (doneSync) { return; } while (shouldFailOver(copy)) { // can we still run if (!isRunAllowed()) { log.trace("Run not allowed, will reject executing exchange: {}", exchange); if (exchange.getException() == null) { exchange.setException(new RejectedExecutionException()); } // we cannot process so invoke callback callback.done(false); } attempts.incrementAndGet(); // are we exhausted by attempts? if (maximumFailoverAttempts > -1 && attempts.get() > maximumFailoverAttempts) { log.trace("Breaking out of failover after {} failover attempts", attempts); break; } index.incrementAndGet(); counter.incrementAndGet(); if (index.get() >= processors.size()) { // out of bounds if (isRoundRobin()) { log.trace("Failover is round robin enabled and therefore starting from the first endpoint"); index.set(0); counter.set(0); } else { // no more processors to try log.trace("Breaking out of failover as we reached the end of endpoints to use for failover"); break; } } // try again but prepare exchange before we failover copy = prepareExchangeForFailover(exchange); Processor processor = processors.get(index.get()); // try to failover using the next processor doneSync = processExchange(processor, exchange, copy, attempts, index, callback, processors); if (!doneSync) { log.trace("Processing exchangeId: {} is continued being processed asynchronously", exchange.getExchangeId()); // the remainder of the failover will be completed async // so we break out now, then the callback will be invoked which then continue routing from where we left here return; } } // remember last good index lastGoodIndex.set(index.get()); // and copy the current result to original so it will contain this result of this eip if (copy != null) { ExchangeHelper.copyResults(exchange, copy); } log.debug("Failover complete for exchangeId: {} >>> {}", exchange.getExchangeId(), exchange); // signal callback we are done callback.done(false); } } public String toString() { return "FailoverLoadBalancer[" + getProcessors() + "]"; } public String getTraceLabel() { return "failover"; } public ExceptionFailureStatistics getExceptionFailureStatistics() { return statistics; } public void reset() { // reset state lastGoodIndex.set(-1); counter.set(-1); statistics.reset(); } @Override protected void doStart() throws Exception { super.doStart(); // reset state reset(); } @Override protected void doStop() throws Exception { super.doStop(); // noop } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy