Skip to content

Commit

Permalink
Initial working draft of circuit breaker retry policy
Browse files Browse the repository at this point in the history
User can manually configure a CircuitBreakerRetryPolicy
but needs to be careful to use stateful retry and
a RetryState where rollbackFor() is always false so that the
recovery is applied.

Better (probably) is to use @CIRCUITBREAKER.
  • Loading branch information
Dave Syer committed Aug 23, 2016
1 parent 51523c0 commit 9af84c0
Show file tree
Hide file tree
Showing 11 changed files with 869 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@

package org.springframework.retry.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import org.springframework.aop.IntroductionInterceptor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
Expand All @@ -34,12 +34,14 @@
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.backoff.ExponentialRandomBackOffPolicy;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.backoff.Sleeper;
import org.springframework.retry.backoff.UniformRandomBackOffPolicy;
import org.springframework.retry.interceptor.MethodArgumentsKeyGenerator;
import org.springframework.retry.interceptor.MethodInvocationRecoverer;
import org.springframework.retry.interceptor.NewMethodArgumentsIdentifier;
import org.springframework.retry.interceptor.RetryInterceptorBuilder;
import org.springframework.retry.policy.CircuitBreakerRetryPolicy;
import org.springframework.retry.policy.MapRetryContextCache;
import org.springframework.retry.policy.RetryContextCache;
import org.springframework.retry.policy.SimpleRetryPolicy;
Expand Down Expand Up @@ -175,9 +177,28 @@ private MethodInterceptor getStatelessInterceptor(Object target, Method method,
private MethodInterceptor getStatefulInterceptor(Object target, Method method, Retryable retryable) {
RetryTemplate template = new RetryTemplate();
template.setRetryContextCache(this.retryContextCache);
template.setRetryPolicy(getRetryPolicy(retryable));
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));

CircuitBreaker circuit = AnnotationUtils.findAnnotation(method, CircuitBreaker.class);
if (circuit!=null) {
RetryPolicy policy = getRetryPolicy(circuit);
CircuitBreakerRetryPolicy breaker = new CircuitBreakerRetryPolicy(policy);
breaker.setOpenTimeout(circuit.openTimeout());
breaker.setResetTimeout(circuit.resetTimeout());
template.setRetryPolicy(breaker);
template.setBackOffPolicy(new NoBackOffPolicy());
String label = circuit.label();
if (!StringUtils.hasText(label)) {
label = method.toGenericString();
}
return RetryInterceptorBuilder.circuitBreaker()
.retryOperations(template)
.recoverer(getRecoverer(target, method))
.label(label)
.build();
}
RetryPolicy policy = getRetryPolicy(retryable);
template.setRetryPolicy(policy);
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
return RetryInterceptorBuilder.stateful()
.retryOperations(template)
.recoverer(getRecoverer(target, method))
Expand Down Expand Up @@ -207,15 +228,20 @@ public void doWith(Method method) throws IllegalArgumentException,
return new RecoverAnnotationRecoveryHandler<Object>(target, method);
}

private RetryPolicy getRetryPolicy(Retryable retryable) {
Class<? extends Throwable>[] includes = retryable.value();
private RetryPolicy getRetryPolicy(Annotation retryable) {
Map<String, Object> attrs = AnnotationUtils.getAnnotationAttributes(retryable);
@SuppressWarnings("unchecked")
Class<? extends Throwable>[] includes = (Class<? extends Throwable>[]) attrs.get("value");
if (includes.length == 0) {
includes = retryable.include();
@SuppressWarnings("unchecked")
Class<? extends Throwable>[] value = (Class<? extends Throwable>[]) attrs.get("include");
includes = value;
}
Class<? extends Throwable>[] excludes = retryable.exclude();
@SuppressWarnings("unchecked")
Class<? extends Throwable>[] excludes = (Class<? extends Throwable>[]) attrs.get("exclude");
if (includes.length == 0 && excludes.length == 0) {
SimpleRetryPolicy simple = new SimpleRetryPolicy();
simple.setMaxAttempts(retryable.maxAttempts());
simple.setMaxAttempts((Integer) attrs.get("maxAttempts"));
return simple;
}
Map<Class<? extends Throwable>, Boolean> policyMap = new HashMap<Class<? extends Throwable>, Boolean>();
Expand All @@ -225,7 +251,7 @@ private RetryPolicy getRetryPolicy(Retryable retryable) {
for (Class<? extends Throwable> type : excludes) {
policyMap.put(type, false);
}
return new SimpleRetryPolicy(retryable.maxAttempts(), policyMap, true);
return new SimpleRetryPolicy((Integer) attrs.get("maxAttempts"), policyMap, true);
}

private BackOffPolicy getBackoffPolicy(Backoff backoff) {
Expand All @@ -239,24 +265,24 @@ private BackOffPolicy getBackoffPolicy(Backoff backoff) {
policy.setInitialInterval(min);
policy.setMultiplier(backoff.multiplier());
policy.setMaxInterval(max > min ? max : ExponentialBackOffPolicy.DEFAULT_MAX_INTERVAL);
if (sleeper != null) {
policy.setSleeper(sleeper);
if (this.sleeper != null) {
policy.setSleeper(this.sleeper);
}
return policy;
}
if (max > min) {
UniformRandomBackOffPolicy policy = new UniformRandomBackOffPolicy();
policy.setMinBackOffPeriod(min);
policy.setMaxBackOffPeriod(max);
if (sleeper != null) {
policy.setSleeper(sleeper);
if (this.sleeper != null) {
policy.setSleeper(this.sleeper);
}
return policy;
}
FixedBackOffPolicy policy = new FixedBackOffPolicy();
policy.setBackOffPeriod(min);
if (sleeper != null) {
policy.setSleeper(sleeper);
if (this.sleeper != null) {
policy.setSleeper(this.sleeper);
}
return policy;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright 2014 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.springframework.retry.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation for a method invocation that is retryable.
*
* @author Dave Syer
* @author Artem Bilan
* @since 1.1
*
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Retryable(stateful = true)
public @interface CircuitBreaker {

/**
* Exception types that are retryable. Synonym for includes(). Defaults to empty (and
* if excludes is also empty all exceptions are retried).
* @return exception types to retry
*/
Class<? extends Throwable>[] value() default {};

/**
* Exception types that are retryable. Defaults to empty (and if excludes is also
* empty all exceptions are retried).
* @return exception types to retry
*/
Class<? extends Throwable>[] include() default {};

/**
* Exception types that are not retryable. Defaults to empty (and if includes is also
* empty all exceptions are retried).
* @return exception types to retry
*/
Class<? extends Throwable>[] exclude() default {};

/**
* @return the maximum number of attempts (including the first failure), defaults to 3
*/
int maxAttempts() default 3;

/**
* A unique label for the circuit for reporting and state management. Defaults to the
* method signature where the annotation is declared.
*
* @return the label for the circuit
*/
String label() default "";

/**
* If the circuit is open for longer than this timeout then it resets on the next call
* to give the downstream component a chance to respond again.
*
* @return the timeout before an open circuit is reset in milliseconds, defaults to
* 20000
*/
long resetTimeout() default 20000;

/**
* When {@link #maxAttempts()} failures are reached within this timeout, the circuit
* is opened automatically, preventing access to the downstream component.
*
* @return the timeout before an closed circuit is opened in milliseconds, defaults to
* 5000
*/
long openTimeout() default 20000;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2015 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.springframework.retry.interceptor;

/**
* @author Dave Syer
*
*/
public class FixedKeyGenerator implements MethodArgumentsKeyGenerator {

private String label;

public FixedKeyGenerator(String label) {
this.label = label;
}

@Override
public Object getKey(Object[] item) {
return this.label;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@
/**
* Interface that allows method parameters to be identified and tagged by a
* unique key.
*
*
* @author Dave Syer
*
*
*/
public interface MethodArgumentsKeyGenerator {

/**
* Get a unique identifier for the item that can be used to cache it between
* calls if necessary, and then identify it later.
*
* @param item the current item.
*
* @param item the current method arguments (may be null if there are none).
* @return a unique identifier.
*/
Object getKey(Object[] item);
Expand Down
Loading

0 comments on commit 9af84c0

Please sign in to comment.