韧性特性

自7.0版本起,Spring框架核心包含了通用的韧性特性,特别是用于方法调用的 @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) 的快捷方式。

对于高级使用场景,您可以通过在@Retryable中设置自定义的MethodRetryPredicate属性,使用谓词根据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

程序化重试支持

与在ApplicationContext中注册的bean的方法指定重试语义的声明式方法@Retryable相比,RetryTemplate提供了一个用于重试任意代码块的编程式API。spring-doc.cadn.net.cn

具体来说,一个RetryTemplate会执行并可能根据配置的Retryable策略重试 RetryPolicy操作。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 attempts次。spring-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 指定一个或多个要排除的异常类型。

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

自定义谓词可以与includesexcludes结合使用;但是,自定义谓词将在includesexcludes应用之后始终被应用。spring-doc.cadn.net.cn

以下示例展示了如何配置一个RetryPolicy,使其具有4次重试尝试以及一个带有少许抖动的指数退避策略。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