org.apache.camel.processor.loadbalancer.CircuitBreakerLoadBalancer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of camel-core Show documentation
Show all versions of camel-core Show documentation
The Core Camel Java DSL based router
/**
* 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;
public class CircuitBreakerLoadBalancer extends LoadBalancerSupport implements Traceable, CamelContextAware {
private static final int STATE_CLOSED = 0;
private static final int STATE_HALF_OPEN = 1;
private static final int STATE_OPEN = 2;
private final List> exceptions;
private CamelContext camelContext;
private int threshold;
private long halfOpenAfter;
private long lastFailure;
// stateful statistics
private AtomicInteger failures = new AtomicInteger();
private AtomicInteger state = new AtomicInteger(STATE_CLOSED);
private final ExceptionFailureStatistics statistics = new ExceptionFailureStatistics();
public CircuitBreakerLoadBalancer() {
this(null);
}
public CircuitBreakerLoadBalancer(List> exceptions) {
this.exceptions = exceptions;
statistics.init(exceptions);
}
public void setHalfOpenAfter(long halfOpenAfter) {
this.halfOpenAfter = halfOpenAfter;
}
public long getHalfOpenAfter() {
return halfOpenAfter;
}
public void setThreshold(int threshold) {
this.threshold = threshold;
}
public int getThreshold() {
return threshold;
}
public int getState() {
return state.get();
}
@Override
public CamelContext getCamelContext() {
return camelContext;
}
@Override
public void setCamelContext(CamelContext camelContext) {
this.camelContext = camelContext;
}
public List> getExceptions() {
return exceptions;
}
/**
* Has the given Exchange failed
*/
protected boolean hasFailed(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("Failed: {} for exchangeId: {}", answer, exchange.getExchangeId());
return answer;
}
@Override
public boolean isRunAllowed() {
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) {
// can we still run
if (!isRunAllowed()) {
log.trace("Run not allowed, will reject executing exchange: {}", exchange);
if (exchange.getException() == null) {
exchange.setException(new RejectedExecutionException("Run is not allowed"));
}
callback.done(true);
return true;
}
return calculateState(exchange, callback);
}
private boolean calculateState(final Exchange exchange, final AsyncCallback callback) {
boolean output;
if (state.get() == STATE_HALF_OPEN) {
if (failures.get() == 0) {
output = closeCircuit(exchange, callback);
} else {
output = openCircuit(exchange, callback);
}
} else if (state.get() == STATE_OPEN) {
if (failures.get() >= threshold && System.currentTimeMillis() - lastFailure < halfOpenAfter) {
output = openCircuit(exchange, callback);
} else {
output = halfOpenCircuit(exchange, callback);
}
} else if (state.get() == STATE_CLOSED) {
if (failures.get() >= threshold && System.currentTimeMillis() - lastFailure < halfOpenAfter) {
output = openCircuit(exchange, callback);
} else if (failures.get() >= threshold && System.currentTimeMillis() - lastFailure >= halfOpenAfter) {
output = halfOpenCircuit(exchange, callback);
} else {
output = closeCircuit(exchange, callback);
}
} else {
throw new IllegalStateException("Unrecognised circuitBreaker state " + state.get());
}
return output;
}
private boolean openCircuit(final Exchange exchange, final AsyncCallback callback) {
boolean output = rejectExchange(exchange, callback);
state.set(STATE_OPEN);
logState();
return output;
}
private boolean halfOpenCircuit(final Exchange exchange, final AsyncCallback callback) {
boolean output = executeProcessor(exchange, callback);
state.set(STATE_HALF_OPEN);
logState();
return output;
}
private boolean closeCircuit(final Exchange exchange, final AsyncCallback callback) {
boolean output = executeProcessor(exchange, callback);
state.set(STATE_CLOSED);
logState();
return output;
}
private void logState() {
if (log.isDebugEnabled()) {
log.debug(dumpState());
}
}
public String dumpState() {
int num = state.get();
String state = stateAsString(num);
if (lastFailure > 0) {
return String.format("State %s, failures %d, closed since %d", state, failures.get(), System.currentTimeMillis() - lastFailure);
} else {
return String.format("State %s, failures %d", state, failures.get());
}
}
private boolean executeProcessor(final Exchange exchange, final AsyncCallback callback) {
Processor processor = getProcessors().get(0);
if (processor == null) {
throw new IllegalStateException("No processors could be chosen to process CircuitBreaker");
}
// store state as exchange property
exchange.setProperty(Exchange.CIRCUIT_BREAKER_STATE, stateAsString(state.get()));
AsyncProcessor albp = AsyncProcessorConverterHelper.convert(processor);
// Added a callback for processing the exchange in the callback
boolean sync = albp.process(exchange, new CircuitBreakerCallback(exchange, callback));
// We need to check the exception here as albp is use sync call
if (sync) {
boolean failed = hasFailed(exchange);
if (!failed) {
failures.set(0);
} else {
failures.incrementAndGet();
lastFailure = System.currentTimeMillis();
}
} else {
// CircuitBreakerCallback can take care of failure check of the
// exchange
log.trace("Processing exchangeId: {} is continued being processed asynchronously", exchange.getExchangeId());
return false;
}
log.trace("Processing exchangeId: {} is continued being processed synchronously", exchange.getExchangeId());
callback.done(true);
return true;
}
private boolean rejectExchange(final Exchange exchange, final AsyncCallback callback) {
exchange.setException(new RejectedExecutionException("CircuitBreaker Open: failures: " + failures + ", lastFailure: " + lastFailure));
callback.done(true);
return true;
}
private static String stateAsString(int num) {
if (num == STATE_CLOSED) {
return "closed";
} else if (num == STATE_HALF_OPEN) {
return "half opened";
} else {
return "opened";
}
}
public String toString() {
return "CircuitBreakerLoadBalancer[" + getProcessors() + "]";
}
public String getTraceLabel() {
return "circuitbreaker";
}
public ExceptionFailureStatistics getExceptionFailureStatistics() {
return statistics;
}
public void reset() {
// reset state
failures.set(0);
state.set(STATE_CLOSED);
statistics.reset();
}
@Override
protected void doStart() throws Exception {
super.doStart();
// reset state
reset();
}
@Override
protected void doStop() throws Exception {
super.doStop();
// noop
}
class CircuitBreakerCallback implements AsyncCallback {
private final AsyncCallback callback;
private final Exchange exchange;
CircuitBreakerCallback(Exchange exchange, AsyncCallback callback) {
this.callback = callback;
this.exchange = exchange;
}
@Override
public void done(boolean doneSync) {
if (!doneSync) {
boolean failed = hasFailed(exchange);
if (!failed) {
failures.set(0);
} else {
failures.incrementAndGet();
lastFailure = System.currentTimeMillis();
}
}
callback.done(doneSync);
}
}
}