Spring++:整合 Retry 实现重试机制

重试,在项目需求中是非常常见的,例如遇到网络波动等,要求某个接口或者是方法可以最多/最少调用几次;实现重试机制,非得用Retry这个重试框架吗?

那肯定不是,相信很多伙伴手写一下控制流程的逻辑也可以达到重试的目的。

那么用Retry的好处是什么?

简单来说,就是优雅。Retry重试框架,支持AOP切入的方式使用,而且能使用注解;

想想,重试次数、重试延迟、重试触发条件、重试的回调方法等等我们都能很轻松结合注解以一种类似配置参数的方式去实现,优雅无疑。

那么,我们接下来就来一起使用 Springboot 整合这个Retry重试框架:

引入 POM 依赖:↓ ↓ ↓ 

<!-- https://mvnrepository.com/artifact/org.springframework.retry/spring-retry -->
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.2.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId >
    <artifactId>aspectjweaver</artifactId >
    <version>1.6.11</version >
</dependency>

然后创建一个测试重试的 TestRetryService.java:

public interface TestRetryService {
    int dignifiedTest(Integer code) throws Exception;
}

然后是 TestRetryServiceImpl.java:

@Service("testRetryService")
public class TestRetryServiceImpl implements TestRetryService {

    /**
     *  @Retryable : 注解方式标记当前方法会使用重试机制
     * 里面的 value: 重试的触发机制,当遇到Exception异常的时候,触发;
     *            maxAttempts: 重试的次数(包括第一次调用,也就是说如果设置3次,调用一次后,如果一直失败触发重试,那么还当前方法还会调用2次);
     *            delay:重试的延迟时间,也就是距离上一次重试方法调用的间隔,单位毫秒
     *            multiplier: delay间隔时间的倍数,也就是说,第一次重试间隔如果是2000ms,那第二次重试的时候就是2000ms 乘以这个倍数1.5,就是3000ms;
     * <p>
     *           maxDelay:重试次数之间的最大时间间隔,默认为0,即忽略,如果小于delay的设置,则默认为30000L;
     *
     * @param code
     * @return
     * @throws Exception
     */

    @Override
    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 1.5))
    public int dignifiedTest(Integer code) throws Exception {
        System.out.println("dignifiedTest被调用,时间:" + LocalTime.now());
        if (code == 0) {
            throw new Exception("情况不对头!");
        }
        System.out.println("dignifiedTest被调用,情况对头了!");

        return 200;
    }

    /**
     * 当重试到达指定次数时,被注解的方法将被回调,可以在该方法中进行日志处理。需要注意的是发生的异常和入参类型一致时才会回调。
     * @param e
     * @return
     */
    @Recover
    public int recover(Exception e) {
        System.out.println("回调方法执行!!!!");
        //记日志到数据库 或者调用其余的方法
        return 400;
    }

}

到这里,已经整合完毕,最后剩下测试了,在测试前,我们先一起来看看代码里面的关键信息的意义:

可以看到代码里面,这个方法上面加上了注解 :↓

@Retryable(value = Exception.class,maxAttempts = 3,backoff = @Backoff(delay = 2000,multiplier = 1.5,maxDelay=360000L))

@Retryable : 注解方式标记当前方法会使用重试机制 里面的

             value: 重试的触发机制,当遇到Exception异常的时候,触发;

maxAttempts: 重试的次数(包括第一次调用,也就是说如果设置3次,调用一次后,如果一直失败触发重试,那么还当前方法还会调用2次);

              delay:重试的延迟时间,也就是距离上一次重试方法调用的间隔,单位毫秒

       multiplier: delay间隔时间的倍数,也就是说,第一次重试间隔如果是2000ms,那第二次重试的时候就是2000ms 乘以这个倍数1.5,就是3000ms;

       maxDelay:重试次数之间的最大时间间隔,默认为0,即忽略,如果小于delay的设置,则默认为30000L;

再来看下面的这个小方法:

 /**
     * 当重试到达指定次数时,被注解的方法将被回调,可以在该方法中进行日志处理。需要注意的是发生的异常和入参类型一致时才会回调。
     * @param e
     * @return
     */
    @Recover
    public int recover(Exception e) {
        System.out.println("回调方法执行!!!!");
        //记日志到数据库 或者调用其余的方法
        return 400;
    }

这个方法用到了@Recover,也就是用注解方式标记当期方法为回调方法,可以看到传参里面写的是 Exception e,这个是作为回调的接头暗号

(重试次数用完了,还是失败,我们抛出这个Exception e通知触发这个回调方法)。

PS:该回调方法与重试方法写在同一个实现类里面。

最后:启动类上开启重试注解

@EnableRetry

二、自己封装 重试机制工具类

如要更加完善,可参考以下方式进行重构:↓

public class RetryUtil {
    private static ThreadLocal<Integer> retryTimesInThread = new ThreadLocal<>();

    /**
     * 设置当前方法重试次数
     *
     * @param retryTimes
     * @return
     */
    public static RetryUtil setRetryTimes(Integer retryTimes) {
        if (retryTimesInThread.get() == null)
            retryTimesInThread.set(retryTimes);
        return new RetryUtil();
    }

    /**
     * 重试当前方法
     * <p>按顺序传入调用者方法的所有参数</p>
     *
     * @param args
     * @return
     */
    public Object retry(Object... args) {
        try {
            Integer retryTimes = retryTimesInThread.get();
            if (retryTimes <= 0) {
                retryTimesInThread.remove();
                return null;
            }
            retryTimesInThread.set(--retryTimes);
            String upperClassName = Thread.currentThread().getStackTrace()[2].getClassName();
            String upperMethodName = Thread.currentThread().getStackTrace()[2].getMethodName();

            Class clazz = Class.forName(upperClassName);
            Object targetObject = clazz.newInstance();
            Method targetMethod = null;
            for (Method method : clazz.getDeclaredMethods()) {
                if (method.getName().equals(upperMethodName)) {
                    targetMethod = method;
                    break;
                }
            }
            if (targetMethod == null)
                return null;
            targetMethod.setAccessible(true);
            return targetMethod.invoke(targetObject, args);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

使用方式:

 @Test
public void mainMethod() {
     subMethod("123", "123");
}
public void subMethod(String param1, String param2) {
     System.out.println(param1 + param2);
   // 出现异常的时候 可以调用 RetryUtil.setRetryTimes(
3).retry(param1, param2); }

 

posted @ 2021-09-16 13:04  coding++  阅读(631)  评论(0编辑  收藏  举报