【问题标题】:How to wrap exception on exhausted retries with Spring Retry如何使用 Spring Retry 在用尽重试时包装异常
【发布时间】:2019-10-17 11:24:31
【问题描述】:

上下文:

我正在使用 spring-retry 来重试 restTemplate 调用。

restTemplate 调用是从 kafka 侦听器调用的。 kafka 监听器也被配置为错误重试(如果在这个过程中抛出任何异常,不仅是 restTemplate 调用)。

目标:

当错误来自已用尽的重试模板时,我想防止 kafka 重试。

实际行为:

当 retryTemplate 用尽所有重试时,会抛出原始异常。从而阻止我确定错误是否由 retryTemplate 重试。

期望的行为:

当 retryTemplate 耗尽所有重试时,将原始异常包装在 RetryExhaustedException 中,这将允许我将其从 kafka 重试中列入黑名单。

问题:

我该怎么做这样的事情?

谢谢

编辑

重试模板配置:

RetryTemplate retryTemplate = new RetryTemplate();

FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(1000);
retryTemplate.setBackOffPolicy(backOffPolicy);

Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>();
retryableExceptions.put(FunctionalException.class, false);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(3, retryableExceptions, true, true);
retryTemplate.setRetryPolicy(retryPolicy);

retryTemplate.setThrowLastExceptionOnExhausted(false);

卡夫卡错误处理程序

public class DefaultErrorHandler implements ErrorHandler {

@Override
public void handle(Exception thrownException, ConsumerRecord<?, ?> data) {
    Throwable exception = Optional.ofNullable(thrownException.getCause()).orElse(thrownException);

    // TODO if exception as been retried in a RetryTemplate, stop it to prevent rollback and send it to a DLQ
    // else rethrow exception, it will be rollback and handled by AfterRollbackProcessor to be retried
    throw new KafkaException("Could not handle exception", thrownException);
   }
}

监听卡夫卡:

@KafkaListener
public void onMessage(ConsumerRecord<String, String> record) {
    retryTemplate.execute((args) -> {
        throw new RuntimeException("Should be catched by ErrorHandler to prevent rollback");
    }
    throw new RuntimeException("Should be retried by afterRollbackProcessor");
}

【问题讨论】:

    标签: spring-retry


    【解决方案1】:

    只需使用配置为将RetryExhaustedException 分类为不可重试的SimplyRetryPolicy 配置侦听器重试模板。

    请务必将 traverseCauses 属性设置为 true,因为容器会将所有侦听器异常包装在 ListenerExecutionFailedException 中。

    /**
     * Create a {@link SimpleRetryPolicy} with the specified number of retry
     * attempts. If traverseCauses is true, the exception causes will be traversed until
     * a match is found. The default value indicates whether to retry or not for exceptions
     * (or super classes) are not found in the map.
     *
     * @param maxAttempts the maximum number of attempts
     * @param retryableExceptions the map of exceptions that are retryable based on the
     * map value (true/false).
     * @param traverseCauses is this clause traversable
     * @param defaultValue the default action.
     */
    public SimpleRetryPolicy(int maxAttempts, Map<Class<? extends Throwable>, Boolean> retryableExceptions,
            boolean traverseCauses, boolean defaultValue) {
    

    编辑

    使用

    template.execute((args) -> {...}, (context) -> throw new Blah(context.getLastThrowable()));
    

    【讨论】:

    • 我已经将具有 traverseCauses 的 SimpleRetryPolicy 配置为 true。但这会抛出原始异常,然后将其包装在 LEFE 中。我想要的是一个 RetryExhaustedException 被触发,而不是原始异常。这将允许我在 ErrorHandler 中执行 if (thrownException instanceof RetryExhaustedException)
    • 您需要编辑问题并显示您的代码 - 容器无法将您的 RetryExhaustedException 从链的顶部剥离 - 它对侦听器异常一无所知。我假设你是从你的恢复回调中抛出的。如果没有恢复回调,则模板会抛出 ExhaustedRetryException 包装您的原始异常,除非您已将 throwLastExceptionOnExhausted 设置为 true,这将导致您描述的行为。
    • 编辑了我的问题,但由于 RetryTemplate 类中的状态为 null,因此未使用 throwLastExceptionOnExhausted
    • 啊——明白了;对不起;在这种情况下,您应该在 execute() 中添加一个恢复回调并将您的包装器扔在那里。我用一个例子编辑了我的答案。
    • 请注意,如果需要,您可以使用context.setAttribute(...); 将任意信息从RetryCallback 传递到RecoveryCallback
    猜你喜欢
    • 2020-07-23
    • 1970-01-01
    • 2019-03-27
    • 2019-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多