弹性特性

自 7.0 版本起,核心 Spring Framework 包含了通用的弹性功能,特别是用于方法调用的 @Retryable@ConcurrencyLimit 注解,以及 编程式重试支持spring-doc.cadn.net.cn

@Retryable

@Retryable 是一个注解, 用于指定单个方法的重试特性(当注解声明在方法级别时), 或指定给定类层次结构中所有通过代理调用的方法的重试特性(当注解声明在类型级别时)。spring-doc.cadn.net.cn

@Retryable
public void sendNotification() {
    this.jmsClient.destination("notifications").send(...);
}

默认情况下,方法调用在抛出任何异常时都会进行重试:在初次失败后最多重试 3 次(maxRetries = 3),每次重试之间间隔 1 秒。spring-doc.cadn.net.cn

一个 @Retryable 方法将至少被调用一次,并最多重试 maxRetries 次,其中 maxRetries 是最大重试尝试次数。具体来说,total attempts = 1 initial attempt + maxRetries attemptsspring-doc.cadn.net.cn

例如,如果将 maxRetries 设置为 4,则 @Retryable 方法将至少被调用一次,最多被调用 5 次。spring-doc.cadn.net.cn

如有必要,可以针对每个方法进行专门调整——例如,通过 includesexcludes 属性来缩小重试所涵盖的异常范围。所提供的异常类型将与失败调用所抛出的异常及其嵌套原因进行匹配。spring-doc.cadn.net.cn

@Retryable(MessageDeliveryException.class)
public void sendNotification() {
    this.jmsClient.destination("notifications").send(...);
}
@Retryable(MessageDeliveryException.class)@Retryable(includes = MessageDeliveryException.class) 的简写形式。

对于高级使用场景,您可以通过 MethodRetryPredicate 注解中的 predicate 属性指定一个自定义的 @Retryable,该谓词将根据一个 Method 和给定的 Throwable 来判断是否重试失败的方法调用——例如,通过检查 Throwable 的消息。spring-doc.cadn.net.cn

自定义谓词可以与 includesexcludes 结合使用;然而,自定义谓词总是在 includesexcludes 应用之后才被应用。spring-doc.cadn.net.cn

或者进行4次重试,并采用带少量抖动的指数退避策略:spring-doc.cadn.net.cn

@Retryable(
  includes = MessageDeliveryException.class,
  maxRetries = 4,
  delay = 100,
  jitter = 10,
  multiplier = 2,
  maxDelay = 1000)
public void sendNotification() {
    this.jmsClient.destination("notifications").send(...);
}

最后但同样重要的是,@Retryable 也适用于具有响应式返回类型的响应式方法,它会使用 Reactor 的重试功能对管道进行装饰:spring-doc.cadn.net.cn

@Retryable(maxRetries = 4, delay = 100)
public Mono<Void> sendNotification() {
    return Mono.from(...); (1)
}
1 这个原始的Mono将被装饰上一个重试规范。

有关各种特性的详细信息,请参阅 @Retryable 中可用的注解属性。spring-doc.cadn.net.cn

@Retryable 注解中的多个属性提供了 String 类型的变体,支持属性占位符和 SpEL 表达式,可作为上述示例中所使用的特定类型注解属性的替代方案。

@ConcurrencyLimit

@ConcurrencyLimit 是一个注解,用于为单个方法(在方法级别声明该注解)或给定类层次结构中所有通过代理调用的方法(在类型级别声明该注解)指定并发限制。spring-doc.cadn.net.cn

@ConcurrencyLimit(10)
public void sendNotification() {
    this.jmsClient.destination("notifications").send(...);
}

这是为了防止目标资源被过多线程同时访问,其效果类似于线程池或连接池的池大小限制:当达到限制时,会阻塞对资源的访问。spring-doc.cadn.net.cn

您可以选择将限制设置为1,从而有效地锁定对目标bean实例的访问:spring-doc.cadn.net.cn

@ConcurrencyLimit(1)
public void sendNotification() {
    this.jmsClient.destination("notifications").send(...);
}

这种限制在使用虚拟线程时特别有用,因为通常不会设置线程池上限。对于异步任务,可以通过 SimpleAsyncTaskExecutor 进行约束。 对于同步调用,此注解通过 ConcurrencyThrottleInterceptor 提供等效行为,该功能自 Spring Framework 1.0 起就已存在,可用于与 AOP 框架进行编程式使用。spring-doc.cadn.net.cn

@ConcurrencyLimit 还具有一个 limitString 属性,该属性提供属性占位符和 SpEL 支持,作为上述基于 int 的示例的替代方案。

启用弹性方法

像 Spring 许多核心的基于注解的功能一样,@Retryable@ConcurrencyLimit 被设计为元数据,您可以选择遵循或忽略它们。启用弹性注解处理的最便捷方式是在相应的 @Configuration 类上声明 @EnableResilientMethodsspring-doc.cadn.net.cn

或者,也可以通过在上下文中定义一个 RetryAnnotationBeanPostProcessorConcurrencyLimitBeanPostProcessor bean 来单独启用这些注解。spring-doc.cadn.net.cn

编程式重试支持

@Retryable(它为注册在 ApplicationContext 中的 Bean 内的方法提供声明式重试语义指定方式)不同, RetryTemplate 提供了一种用于重试任意代码块的编程式 API。spring-doc.cadn.net.cn

具体来说,RetryTemplate 会根据配置的 RetryPolicy 执行并可能重试 Retryable 操作。spring-doc.cadn.net.cn

var retryTemplate = new RetryTemplate(); (1)

retryTemplate.execute(
        () -> jmsClient.destination("notifications").send(...));
1 隐式使用 RetryPolicy.withDefaults()

默认情况下,可重试的操作在抛出任何异常时都会进行重试:在初次失败后最多重试 3 次(maxRetries = 3),且每次重试之间间隔 1 秒。spring-doc.cadn.net.cn

一个可重试的操作将至少执行一次,最多重试 maxRetries 次,其中 maxRetries 是最大重试尝试次数。具体来说, total attempts = 1 initial attempt + maxRetries attemptsspring-doc.cadn.net.cn

例如,如果将 maxRetries 设置为 4,则可重试的操作将至少执行一次,最多执行 5 次。spring-doc.cadn.net.cn

如果仅需自定义重试次数,您可以使用 RetryPolicy.withMaxRetries() 工厂方法,如下所示。spring-doc.cadn.net.cn

var retryTemplate = new RetryTemplate(RetryPolicy.withMaxRetries(4)); (1)

retryTemplate.execute(
        () -> jmsClient.destination("notifications").send(...));
1 显式使用 RetryPolicy.withMaxRetries(4)

如果你需要缩小重试的异常类型范围,可以通过 includes()excludes() 构建器方法来实现。所提供的异常类型将与失败操作所抛出的异常及其嵌套原因进行匹配。spring-doc.cadn.net.cn

var retryPolicy = RetryPolicy.builder()
        .includes(MessageDeliveryException.class) (1)
        .excludes(...) (2)
        .build();

var retryTemplate = new RetryTemplate(retryPolicy);

retryTemplate.execute(
        () -> jmsClient.destination("notifications").send(...));
1 指定一个或多个要包含的异常类型。
2 指定一个或多个要排除的异常类型。

对于高级使用场景,您可以通过 Predicate<Throwable> 中的 predicate() 方法指定一个自定义的 RetryPolicy.Builder,该谓词将用于根据给定的 Throwable 判断是否重试失败的操作——例如,通过检查 Throwable 的消息内容。spring-doc.cadn.net.cn

自定义谓词可以与 includesexcludes 结合使用;然而,自定义谓词总是在 includesexcludes 应用之后才被应用。spring-doc.cadn.net.cn

以下示例演示了如何配置一个 RetryPolicy,包含 4 次重试尝试,并采用带有少量抖动(jitter)的指数退避策略。spring-doc.cadn.net.cn

var retryPolicy = RetryPolicy.builder()
        .includes(MessageDeliveryException.class)
        .maxRetries(4)
        .delay(Duration.ofMillis(100))
        .jitter(Duration.ofMillis(10))
        .multiplier(2)
        .maxDelay(Duration.ofSeconds(1))
        .build();

var retryTemplate = new RetryTemplate(retryPolicy);

retryTemplate.execute(
        () -> jmsClient.destination("notifications").send(...));

一个 RetryListener 可以向 RetryTemplate 注册,以响应在关键重试阶段(重试尝试之前、重试尝试之后等)发布的事件,并且您可以通过 CompositeRetryListener 组合多个监听器。spring-doc.cadn.net.cn

尽管 RetryPolicy 的工厂方法和构建器 API 涵盖了大多数常见配置场景,但您可以实现自定义的 RetryPolicy,以完全控制应触发重试的异常类型以及要使用的 BackOff 策略。请注意,您还可以通过 RetryPolicy.Builder 中的 backOff() 方法配置自定义的 BackOff 策略。spring-doc.cadn.net.cn