阅读(4755) (0)

Micronaut 重试 Advice

2023-02-23 11:12:27 更新

在分布式系统和微服务环境中,失败是您必须计划的事情,并且通常希望在失败时尝试重试操作。如果第一次你没有成功再试一次!

考虑到这一点,Micronaut 包含一个 Retryable 注释。

简单重试

最简单的重试形式就是将 @Retryable 注释添加到类型或方法。 @Retryable 的默认行为是重试三次,每次重试之间的指数延迟为一秒。 (第一次尝试延迟 1 秒,第二次尝试延迟 2 秒,第三次尝试延迟 3 秒)。

例如:

简单重试示例

 Java Groovy  Kotlin 
@Retryable
public List<Book> listBooks() {
    // ...
@Retryable
List<Book> listBooks() {
    // ...
@Retryable
open fun listBooks(): List<Book> {
    // ...

对于上面的示例,如果 listBooks() 方法抛出 RuntimeException,则会重试直到达到最大尝试次数。

@Retryable 注释的乘数值可用于配置用于计算重试之间延迟的乘数,从而允许指数重试支持。

要自定义重试行为,请设置 attempts 和 delay 成员,例如配置五次尝试和两秒延迟:

设置重试次数

 Java Groovy  Kotlin 
@Retryable(attempts = "5",
           delay = "2s")
public Book findBook(String title) {
    // ...
@Retryable(attempts = "5",
           delay = "2s")
Book findBook(String title) {
    // ...
@Retryable(attempts = "5",
           delay = "2s")
open fun findBook(title: String): Book {
    // ...

请注意尝试和延迟是如何定义为字符串的。这是为了通过注释元数据支持可配置性。例如,您可以允许使用属性占位符解析来配置重试策略:

通过配置设置重试

 Java Groovy  Kotlin 
@Retryable(attempts = "${book.retry.attempts:3}",
           delay = "${book.retry.delay:1s}")
public Book getBook(String title) {
    // ...
@Retryable(attempts = '${book.retry.attempts:3}',
           delay = '${book.retry.delay:1s}')
Book getBook(String title) {
    // ...
@Retryable(attempts = "\${book.retry.attempts:3}",
           delay = "\${book.retry.delay:1s}")
open fun getBook(title: String): Book {
    // ...

有了上面的内容,如果在配置中指定了 book.retry.attempts,它就会通过注释元数据绑定到 @Retryable 注释的 attempts 成员的值。

Reactive Retry(反应性重试)

@Retryable 通知也可以应用于返回反应类型的方法,例如 Publisher(Project Reactor 的 Flux 或 RxJava 的 Flowable)。例如:

将重试策略应用于反应类型

 Java Groovy  Kotlin 
@Retryable
public Publisher<Book> streamBooks() {
    // ...
@Retryable
Flux<Book> streamBooks() {
    // ...
@Retryable
open fun streamBooks(): Flux<Book> {
    // ...

在这种情况下,@Retryable 建议将重试策略应用于反应类型。

Circuit Breaker(断路器)

重试在微服务环境中很有用,但在某些情况下,过多的重试会使系统不堪重负,因为客户端会反复重新尝试失败的操作。

断路器模式旨在通过允许一定数量的失败请求然后打开一个保持打开状态一段时间的电路来解决此问题,然后再允许进行额外的重试尝试。

CircuitBreaker 注释是 @Retryable 注释的变体,它支持一个 reset 成员,该成员指示电路在重置之前应保持打开状态的时间(默认值为 20 秒)。

应用断路器建议

 Java Groovy  Kotlin 
@CircuitBreaker(reset = "30s")
public List<Book> findBooks() {
    // ...
@CircuitBreaker(reset = "30s")
List<Book> findBooks() {
    // ...
@CircuitBreaker(reset = "30s")
open fun findBooks(): List<Book> {
    // ...

上面的示例重试 findBooks 方法 3 次,然后打开电路 30 秒,重新抛出原始异常并防止潜在的下游流量(例如 HTTP 请求和 I/O 操作)淹没系统。

Factory Bean Retry

当 @Retryable 应用于 bean 工厂方法时,它的行为就像注释被放置在返回的类型上一样。当调用返回对象上的方法时,重试行为适用。请注意,bean 工厂方法本身不会重试。如果您希望重试创建 bean 的功能,则应将其委托给另一个应用了 @Retryable 注释的单例。

例如:

@Factory (1)
public class Neo4jDriverFactory {
    ...
    @Retryable(ServiceUnavailableException.class) (2)
    @Bean(preDestroy = "close")
    public Driver buildDriver() {
        ...
    }
}
  1. 创建了一个工厂 bean,它定义了创建 bean 的方法

  2. @Retryable 注释用于捕获从驱动程序上执行的方法抛出的异常。

重试事件

您可以将 RetryEventListener 实例注册为 bean,以侦听每次重试操作时发布的 RetryEvent 事件。

此外,您可以为 CircuitOpenEvent 注册事件侦听器,以便在断路器电路打开时收到通知,或者在电路关闭时收到 CircuitClosedEvent 的通知。