简体   繁体   English

使用 Spring-Retry 指定特定于异常的退避策略

[英]Specifying an exception-specific backoff policy with Spring-Retry

I am using Spring-Retry for some database operations.我正在使用Spring-Retry进行一些数据库操作。 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.SQLRecoverableException我重试了 3 次(这假设导致异常的原因是非暂时性的,如果它失败了 3 次),在SQLTransientException我无限期地重试(程序不能在没有访问数据库的情况下做任何事情,所以它可能并继续重试,直到用户决定重新启动服务器),并且在任何其他异常情况下我不会重试。 I use an exponential backoff policy with a base retry of 100ms and a max retry of 30,000ms.我使用指数退避策略,基本重试时间为 100 毫秒,最大重试时间为 30,000 毫秒。

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 .理想情况下,我想对所有SQLRecoverableExceptions使用 100 毫秒的固定退避,并且只将指数退避策略应用于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.我可以通过嵌套重试来实现这一点,但这会大大增加代码的复杂性——如果没有其他选择,我宁愿简单地将指数退避应用于SQLRecoverableExceptionSQLTransientException异常。

Is there a way for me to apply different backoff policies to different exceptions using a single retry template?有没有办法使用单个重试模板将不同的退避策略应用于不同的异常?

Indeed, ExceptionClassifierRetryPolicy is the way to go.确实, ExceptionClassifierRetryPolicy是要走的路。 I didn't manage to make it work with the policyMap though.不过,我没有设法让它与policyMap一起使用。

Here is how I've used it:这是我如何使用它:

@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;
            }
        });
    }
}

Then, you just have to set it on the retry template :然后,您只需在重试模板上设置它:

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

//...

RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy( yourRetryPolicy );

The accepted answer only deals with exception-specific RetryPolicy instances.接受的答案仅处理特定于异常的 RetryPolicy 实例。 Spring doesn't provide any functionality out of the box for exception-specific BackOffPolicy instances. Spring 没有为特定于异常的 BackOffPolicy 实例提供任何开箱即用的功能。 Luckily it's straightforward to implement.幸运的是,它很容易实现。

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)
    }
}

Then just:那么只需:

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

A little late to the party but when all you need is having different backoff periods depending on the type of the exception, extending FixedBackoffPolicy should do the trick and is pretty straightforward.聚会有点晚了,但是当您需要的是根据异常类型有不同的退避期时,扩展FixedBackoffPolicy应该可以解决问题并且非常简单。 Something along these lines:沿着这些路线的东西:

First you create your backoff policy class that receives a map with different backoff periods per exception type:首先,您创建退避策略类,该类接收每个异常类型具有不同退避周期的映射:

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();
    }
}

then you have to create a custom interceptor such as:那么你必须创建一个自定义拦截器,例如:

@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();
}

and finally, you just configure the custom interceptor in your retryable:最后,您只需在可重试中配置自定义拦截器:

@Retryable(interceptor="myCustomInterceptor")
public @interface MyRetryable {}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM