溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

spring retry, guava retrying 的整合-sisyphus java 重試

發(fā)布時間:2020-08-20 04:55:19 來源:網絡 閱讀:140624 作者:葉止水ryo 欄目:編程語言

Sisyphus

支持過程式編程和注解編程的 java 重試框架。

特性

  • 支持 fluent 過程式編程

  • 基于字節(jié)碼的代理重試

  • 基于注解的重試,允許自定義注解

  • 無縫接入 spring

  • 接口與注解的統(tǒng)一

  • 解決 spring-retry 與 guava-retrying 中的不足之處

設計目的

綜合了 spring-retry 和 gauva-retrying 的優(yōu)勢。

調整一些特性,使其更利于實際使用。

采用 Netty 類似的接口思想,保證接口的一致性,和替換的靈活性。

借鑒 Hibernate-Validator 的設計,允許用戶自定義注解。

spring-retry 與 guava-retrying 中的不足之處

更新記錄

更新記錄

開源地址

sisyphus

快速開始

引入

<plugin>
    <groupId>com.github.houbb</groupId>
    <artifactId>sisyphus-core</artifactId>
    <version>0.0.6</version>
</plugin>

入門代碼

詳情參見 [RetryerTest]()

public void helloTest() {
    Retryer.<String>newInstance()
            .retry(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println("called...");
                    throw new RuntimeException();
                }
            });
}

代碼分析

  • retry

指定一個 callable 的實現。

我們打印一條日志,并且模擬一個程序異常。

日志信息

日志信息

called...
called...
called...

和一些其他異常信息。

重試觸發(fā)的條件,默認是程序發(fā)生了異常

這里的重試間隔默認為沒有時間間隔,一共嘗試3次。(包括第一次程序本身執(zhí)行)

為什么選擇 sisyphus

為什么選擇 sisyphus

作為開發(fā)者,我們一般都會選擇比較著名的框架。

比如 guava-retrying spring-retry。

或者干脆自己寫一個。

為什么不是 guava-retrying/spring-retry

java retry 這篇文章中我列舉了常見的實現方式
以及上述的兩種框架,也講述了其中的不足。

guava-retrying 優(yōu)缺點

優(yōu)點

  • 使用靈活

  • fluent 優(yōu)雅寫法

  • 提供足夠多的實現

缺點

  • 沒有默認基于注解的實現

  • 重試策略設計并不友好

spring-retry

優(yōu)點

  • 使用簡單

缺點

  • 重試條件單一

  • 重試等待策略單一

  • 無法自定義注解

為什么不自己寫一個

個人感受

我作為一名開發(fā),平時說實在的,看到重試。

我肯定會偷懶寫一個 for 循環(huán),重試幾次就結束了。

因為時間不允許。

如果你更勤快一點,就可以選擇 spring-retry/guava-retrying。如果你熟悉他們的優(yōu)缺點的話。

如果你渴望創(chuàng)造

sisyphus 所有的實現都是基于接口的。

你完全可以實現自己的實現,所有的東西基本完全可以被替換。

當然一些常見的策略實現,項目的基本框架都有詳盡的注釋,當做參考也可以有一點幫助。

sisyphus 做的更多的事情

netty 的靈感

參考了 netty 的設計,保證接口實現的一致性。

而且 sisyphus 還做了更多,還保證了接口和注解之間的一致性。

使用引導類,保證使用時的便利性,后期拓展的靈活性。

hibernate-validator

hibernate-validator 的作者是我知道為數不多的對于 java 注解應用很棒的開發(fā)者。(雖然所知甚少)

自定義注解就是從這個框架中學來的。

與 spring 為伍

spring 基本與我們的代碼形影不離,所以你可以很簡單的結合 spring.

就像你使用 spring-retry 一樣。

sisyphus 模塊簡介

模塊劃分

sisyphus 在模塊劃分的時候考慮到使用者的方便,主要有幾個模塊:

sisyphus-api

接口定義模塊,是最基礎的部分。

會被 sisyphus-core 默認依賴。

一般不需要引入,如果你想根據它實現自己的重試框架,不妨一試。

sisyphus-core

對于 sisyphus-api 模塊的默認實現。

并且添加易于使用的 Fluent 引導類,可以很方便的寫出聲明式的重試代碼。

sisyphus-annotation

sisyphus 的注解實現模塊。

(1)基于字節(jié)碼實現的代理重試,可以不依賴 spring。平時使用也更加靈活

(2)允許自定義注解及其實現。使用者可以編寫屬于自己的重試注解。

sisyphus-spring

spring 做為 java 開發(fā)的引導者。自然是要支持的。

你可以和使用 spring-retry 一樣方便的使用 sisyphus-spring。

模塊間的依賴關系

sisyphus-api
sisyphus-core
sisyphus-annotation
sisyphus-spring
sisyphus-test

sisyphus-api 是基礎的,靈活性最高。

sisyphus-spring 是最簡單易用的,靈活性相對較差。

sisyphus-test 僅僅用作測試,不用外部引入。

sisyphus 配置概覽

為了滿足更加方便的配置,Retryer 類提供了許多可以配置的信息。

默認配置

/**
 * 默認配置測試
 */
public void defaultConfigTest() {
    Retryer.<String>newInstance()
            .condition(RetryConditions.hasExceptionCause())
            .retryWaitContext(RetryWaiter.<String>retryWait(NoRetryWait.class).context())
            .maxAttempt(3)
            .listen(RetryListens.noListen())
            .recover(Recovers.noRecover())
            .callable(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println("called...");
                    throw new RuntimeException();
                }
            }).retryCall();
}

和下面的代碼是等價的:

public void helloTest() {
    Retryer.<String>newInstance()
            .callable(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println("called...");
                    throw new RuntimeException();
                }
            }).retryCall();
}

方法說明

condition

重試觸發(fā)的條件,可以指定多個條件。

默認為拋出異常。

retryWaitContext

重試等待的策略,可以指定多個。

默認為不做任何等待。

maxAttempt

指定最大重試次數,包括第一次執(zhí)行。

默認值:3 次。

listen

指定重試的監(jiān)聽實現,默認為不做監(jiān)聽。

recover

當重試完成之后,依然滿足重試條件,則可以指定恢復的策略。

默認不做恢復。

callable

待重試執(zhí)行的方法。

retryCall

觸發(fā)重試執(zhí)行。

接口的詳細介紹

接口及其實現

所有的接口,都可以直接查看對應的子類實例。

用戶自定義

基于替換的靈活性,用戶可以實現接口,定義更符合自己業(yè)務的實現。

sisyphus 注解

配置具有很高的靈活性,但是對于開發(fā)人員的使用,就沒有注解那樣簡單靈活。

所以本框架也實現了基于注解的重試。

設計的規(guī)范

保證接口和注解二者的統(tǒng)一性。

maven 引入

<dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>sisyphus-annotation</artifactId>
    <version>${project.version}</version>
</dependency>

注解

核心注解主要有兩個。

Retry

用于指定重試的相關配置。

/**
 * 重試注解
 * 1. 實際需要,只允許放在方法上。
 * 2. 如果放在接口上,是否所有的子類都生效?為了簡單明確,不提供這種實現。
 * 3. 保持注解和接口的一致性。{@link com.github.houbb.sisyphus.api.core.Retry} 接口
 * @author binbin.hou
 * @since 0.0.3
 */
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@RetryAble(DefaultRetryAbleHandler.class)
public @interface Retry {

    /**
     * 重試類實現
     * @return 重試
     * @since 0.0.5
     */
    Class<? extends com.github.houbb.sisyphus.api.core.Retry> retry() default DefaultRetry.class;

    /**
     * 最大嘗試次數
     * 1. 包含方法第一次正常執(zhí)行的次數
     * @return 次數
     */
    int maxAttempt() default 3;

    /**
     * 重試觸發(fā)的場景
     * @return 重試觸發(fā)的場景
     */
    Class<? extends RetryCondition> condition() default ExceptionCauseRetryCondition.class;

    /**
     * 監(jiān)聽器
     * 1. 默認不進行監(jiān)聽
     * @return 監(jiān)聽器
     */
    Class<? extends RetryListen> listen() default NoRetryListen.class;

    /**
     * 恢復操作
     * 1. 默認不進行任何恢復操作
     * @return 恢復操作對應的類
     */
    Class<? extends Recover> recover() default NoRecover.class;

    /**
     * 等待策略
     * 1. 支持指定多個,如果不指定,則不進行任何等待,
     * @return 等待策略
     */
    RetryWait[] waits() default {};

}

RetryWait

用于指定重試的等待策略。

package com.github.houbb.sisyphus.annotation.annotation;

import com.github.houbb.sisyphus.annotation.annotation.metadata.RetryWaitAble;
import com.github.houbb.sisyphus.annotation.handler.impl.DefaultRetryWaitAbleHandler;
import com.github.houbb.sisyphus.core.constant.RetryWaitConst;
import com.github.houbb.sisyphus.core.support.wait.NoRetryWait;

import java.lang.annotation.*;

/**
 * 重試等待策略
 * 1. 為了對應重試策略,所有的內置注解應該實現當前的注解。
 * 2. 是否允許自定義注解?
 *
 * 當注解+對象同時出現的時候,視為組合。
 *
 * @author binbin.hou
 * @since 0.0.3
 */
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Target(ElementType.ANNOTATION_TYPE)
@RetryWaitAble(DefaultRetryWaitAbleHandler.class)
public @interface RetryWait {

    /**
     * 默認值
     * 1. fixed 模式,則對應固定等待時間
     * 2. 遞增
     * @return 默認值
     */
    long value() default RetryWaitConst.VALUE_MILLS;

    /**
     * 最小值
     * @return 最小值
     */
    long min() default RetryWaitConst.MIN_MILLS;

    /**
     * 最大值
     * @return 最大值
     */
    long max() default RetryWaitConst.MAX_MILLS;

    /**
     * 影響因數
     * 1. 遞增重試,默認為 {@link RetryWaitConst#INCREASE_MILLS_FACTOR}
     * 2. 指數模式。默認為 {@link RetryWaitConst#MULTIPLY_FACTOR}
     * @return 影響因數
     */
    double factor() default Double.MIN_VALUE;

    /**
     * 指定重試的等待時間 class 信息
     * @return 重試等待時間 class
     */
    Class<? extends com.github.houbb.sisyphus.api.support.wait.RetryWait> retryWait() default NoRetryWait.class;

}

注解的使用

定義好了注解,肯定要有注解的相關使用。

關于注解的使用,主要有兩種方式。

Proxy+CGLIB

基于代理模式和字節(jié)碼增強。

如果是項目中沒有使用 spring,直接使用這種方式比較方便。

Spring-AOP

可以和 spring 直接整合。

使用方式和 spring-retry 是一樣的。

sisyphus 代理模板

目的

為了便于用戶更加方便地使用注解,同時又不依賴 spring。

提供基于代碼模式+字節(jié)碼增強實現的方式。

使用案例

maven 引入

引入注解相關模塊。

<dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>sisyphus-annotation</artifactId>
    <version>${project.version}</version>
</dependency>

定義測試方法

以下測試代碼可以參考 [spring-test]() 模塊。

  • MenuServiceImpl.java
public class MenuServiceImpl {

    public void queryMenu(long id) {
        System.out.println("查詢菜單...");
        throw new RuntimeException();
    }

    @Retry
    public void queryMenuRetry(long id) {
        System.out.println("查詢菜單...");
        throw new RuntimeException();
    }

}

測試

使用 RetryTemplate 進行測試

無重試注解的方法

@Test(expected = RuntimeException.class)
public void templateTest() {
    MenuServiceImpl menuService = RetryTemplate.getProxyObject(new MenuServiceImpl());
    menuService.queryMenu(1);
}
  • 日志信息
查詢菜單...

只請求了一次。

有注解的方法

@Test(expected = RuntimeException.class)
public void templateRetryTest() {
    MenuServiceImpl menuService = RetryTemplate.getProxyObject(new MenuServiceImpl());
    menuService.queryMenuRetry(1);
}
  • 日志信息
查詢菜單...
查詢菜單...
查詢菜單...

其他

當然還有更多的配置,可以自行嘗試。

如果你想結合 spring 使用注解,請繼續(xù)往下看。

sisyphus spring 整合

目的

類似于 spring-retry 框架,如果你使用 spring 框架,那么整合本項目將會非常簡單。

注解的方式和過程式編程,二者盡可能的保持一致性,你想從一種方式變?yōu)榱硪环N也比較簡單。

想從 spring-retry 切換到本框架也很方便。

使用示例

maven 引入

<dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>sisyphus-spring</artifactId>
    <version>${project.version}</version>
</dependency>

會默認引入 spring 以及 AOP 相關 jar。

業(yè)務代碼

你可以參考 sisyphus-test 模塊。

下面模擬非常常見的一些業(yè)務方法。

使用 @Retry 標識方法需要進行重試。

  • SpringService.java
public interface SpringService {

    /**
     * 查詢示例代碼
     * @return 結果
     */
    String query();

}
  • SpringServiceImpl.java
import com.github.houbb.sisyphus.annotation.annotation.Retry;
import com.github.houbb.sisyphus.test.service.SpringService;
import org.springframework.stereotype.Service;

/**
 * @author binbin.hou
 * @since 0.0.4
 */
@Service
public class SpringServiceImpl implements SpringService {

    @Override
    @Retry
    public String query() {
        System.out.println("spring service query...");
        throw new RuntimeException();
    }

}

開啟重試

基于注解直接如下配置即可。

使用 @EnableRetry 標識需要開啟重試。

@Configurable
@ComponentScan(basePackages = "com.github.houbb.sisyphus.test.service")
@EnableRetry
public class SpringConfig {
}

測試代碼

import com.github.houbb.sisyphus.test.config.SpringConfig;
import com.github.houbb.sisyphus.test.service.SpringService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4Cla***unner;

/**
 * @author binbin.hou
 * @since 0.0.4
 */
@ContextConfiguration(classes = SpringConfig.class)
@RunWith(SpringJUnit4Cla***unner.class)
public class SpringServiceTest {

    @Autowired
    private SpringService springService;

    @Test(expected = RuntimeException.class)
    public void queryTest() {
        springService.query();
    }

}
  • 日志信息
spring service query...
spring service query...
spring service query...

新特性預定

入參

重試上下文添加入參信息

配置優(yōu)化

提供更加優(yōu)異的配置體驗

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI