13

I am using Spring-Retry for some database operations. On a SQLRecoverableException I retry three times (this assumes that whatever is causing the exception is non-transient if it fails three times), on a SQLTransientException I retry indefinitely (the program can't do anything without access to the database, so it may as well keep retrying until the user decides to reboot the server), and on any other exception I don't retry. I use an exponential backoff policy with a base retry of 100ms and a max retry of 30,000ms.

private static final int MAX_RECOVERABLE_RETRIES = 3;
private static final long INITIAL_INTERVAL = 100;
private static final long MAX_INTERVAL = 30 * 1000;
private static final double MULTIPLIER = 2.0;

public static RetryTemplate databaseTemplate() {
    RetryTemplate template = new RetryTemplate();
    ExceptionClassifierRetryPolicy retryPolicy = new ExceptionClassifierRetryPolicy();
    Map<Class<? extends Throwable>, RetryPolicy> policyMap = new HashMap<>();
    NeverRetryPolicy baseException = new NeverRetryPolicy();
    SimpleRetryPolicy recoverablePolicy = new SimpleRetryPolicy();
    recoverablePolicy.setMaxAttempts(MAX_RECOVERABLE_RETRIES);
    AlwaysRetryPolicy transientPolicy = new AlwaysRetryPolicy();
    policyMap.put(Exception.class, baseException);
    policyMap.put(SQLRecoverableException.class, recoverablePolicy);
    policyMap.put(SQLTransientException.class, transientPolicy);
    retryPolicy.setPolicyMap(policyMap);
    template.setRetryPolicy(retryPolicy);
    ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
    backOffPolicy.setInitialInterval(INITIAL_INTERVAL);
    backOffPolicy.setMaxInterval(MAX_INTERVAL);
    backOffPolicy.setMultiplier(MULTIPLIER);
    template.setBackOffPolicy(backOffPolicy);
    return template;
}

Ideally, I would like to use a fixed backoff of 100ms for all SQLRecoverableExceptions, and only apply the exponential backoff policy to SQLTransientExceptions. I could accomplish this with nested retries, but that will greatly increase the code complexity - given no other option I would prefer to simply apply the exponential backoff to both SQLRecoverableException and SQLTransientException exceptions.

Is there a way for me to apply different backoff policies to different exceptions using a single retry template?

4

3 回答 3

7

确实,ExceptionClassifierRetryPolicy是要走的路。我没有设法使它与policyMap虽然一起工作。

这是我使用它的方式:

@Component("yourRetryPolicy")
public class YourRetryPolicy extends ExceptionClassifierRetryPolicy
{
    @PostConstruct
    public void init()
    {
        final SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
        simpleRetryPolicy.setMaxAttempts( 3 );

        this.setExceptionClassifier( new Classifier<Throwable, RetryPolicy>()
        {
            @Override
            public RetryPolicy classify( Throwable classifiable )
            {
                    if ( classifiable instanceof YourException )
                    {
                            return new NeverRetryPolicy();
                    }
                    // etc...
                    return simpleRetryPolicy;
            }
        });
    }
}

然后,您只需在重试模板上设置它:

@Autowired
@Qualifier("yourRetryPolicy")
private YourRetryPolicy yourRetryPolicy;

//...

RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy( yourRetryPolicy );
于 2015-09-01T07:05:03.877 回答
2

接受的答案仅处理特定于异常的 RetryPolicy 实例。Spring 没有为特定于异常的 BackOffPolicy 实例提供任何开箱即用的功能。幸运的是,它很容易实现。

import org.springframework.classify.Classifier
import org.springframework.classify.ClassifierSupport
import org.springframework.classify.SubclassClassifier
import org.springframework.retry.RetryContext
import org.springframework.retry.backoff.BackOffContext
import org.springframework.retry.backoff.BackOffInterruptedException
import org.springframework.retry.backoff.BackOffPolicy
import org.springframework.retry.backoff.NoBackOffPolicy

class ExceptionClassifierBackoffPolicy implements BackOffPolicy {

    private static class ExceptionClassifierBackoffContext implements BackOffContext, BackOffPolicy {
        Classifier<Throwable, BackOffPolicy> exceptionClassifier
        RetryContext retryContext
        Map<BackOffPolicy, BackOffContext> policyContextMap = [:]

        ExceptionClassifierBackoffContext(Classifier<Throwable, BackOffPolicy> exceptionClassifier, RetryContext retryContext) {
            this.exceptionClassifier = exceptionClassifier
            this.retryContext = retryContext
        }

        @Override
        BackOffContext start(RetryContext context) {
            return null
        }

        @Override
        void backOff(BackOffContext backOffContext) throws BackOffInterruptedException {
            def policy = exceptionClassifier.classify(retryContext.lastThrowable)
            def policyContext = policyContextMap.get(policy)
            if (!policyContext) {
                policyContext = policy.start(retryContext)
                policyContextMap.put(policy, policyContext)
            }
            policy.backOff(policyContext)
        }
    }
    private Classifier<Throwable, BackOffPolicy> exceptionClassifier = new ClassifierSupport<Throwable, BackOffPolicy>(new NoBackOffPolicy());

    void setPolicyMap(Map<Class<? extends Throwable>, BackOffPolicy> policyMap) {
        exceptionClassifier = new SubclassClassifier<Throwable, BackOffPolicy>(policyMap, new NoBackOffPolicy());
    }

    @Override
    BackOffContext start(RetryContext context) {
        return new ExceptionClassifierBackoffContext(exceptionClassifier, context)
    }

    @Override
    void backOff(BackOffContext backOffContext) throws BackOffInterruptedException {
        def classifierBackOffContext = (ExceptionClassifierBackoffContext) backOffContext
        classifierBackOffContext.backOff(backOffContext)
    }
}

然后只是:

BackOffPolicy backOffPolicy = new ExceptionClassifierBackoffPolicy()
def policyMap = [
        (RuntimeException): new FixedBackOffPolicy(backOffPeriod: 1000),
        (IOException)     : new ExponentialRandomBackOffPolicy(initialInterval: 500, maxInterval: 360000, multiplier: 2)
] as Map<Class<? extends Throwable>, BackOffPolicy>
backOffPolicy.policyMap = backoffPolicyMap
于 2019-02-28T19:44:40.583 回答
0

聚会有点晚了,但是当您所需要的只是根据异常的类型有不同的退避期时,扩展FixedBackoffPolicy应该可以解决问题并且非常简单。这些方面的东西:

首先,您创建您的退避策略类,它接收每个异常类型具有不同退避期的映射:

public class MultipleExceptionsBackoffPolicy extends FixedBackoffPolicy 
{
    private Classifier<Throwable, Long> classifier;

    public MultipleExceptionsBackoffPolicy (final Map<Class<? extends Throwable>, Long> throwableBackoffMap) {
        classifier = new SubclassClassifier<>(throwableBackoffMap, 5_000L) // default is 5s
    }

    @Override
    protected void doBackOff() exception BackOffInterruptedException {
        final backoff = classifier.classify(RetrySynchronizationManager.getContext().getLastThrowable());
        setBackOffPeriod(backoff);
        super.doBackOff();
    }
}

那么您必须创建一个自定义拦截器,例如:

@Bean
public Object myCustomInterceptor (){
    var exBackoffMap = Map.of(
        ExceptionTypeOne.class, 2_000L, // If ExceptionTypeOne happens, backoff period is 2s
        ExceptionTypeTwo.class, 7_000L // and if ExceptionTypeTwo happens, the backoff period is 7s
    )
    return RetryInterceptorBuilder
                .stateless()
                .retryPolicy(new SimpleRetryPolicy(3)) // always 3 attempts no matter what
                .backOffPolicy(new MultipleExceptionsBackoffPolicy(exBackoffMap))
                .build();
}

最后,您只需在可重试对象中配置自定义拦截器:

@Retryable(interceptor="myCustomInterceptor")
public @interface MyRetryable {}
于 2021-04-27T18:52:32.823 回答