天天看点

外部接口调用失败重试

文章目录

    • @[toc]
      • 第三方接口调用失败重试
        • 规则
        • 代码
          • `@MyRetry`
          • `MyRetryFactory`
          • `MyRetryTemplate`
          • `ResponseResult`
          • `ThirdCallService`
          • `ThirdCallServiceImpl`
          • `Tester`
            • 结果
        • 总结

第三方接口调用失败重试

规则

  1. 第三方接口调用失败后,相隔3秒后后重试;
  2. 若再次失败则相隔5秒重试,后续不再重试。

代码

@MyRetry

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRetry {

    int retryTimes() default 0;

    int[] retrySecond() default {};

}
           

MyRetryFactory

public class MyRetryFactory {

    public static <T> T getRetryServiceProxy(T realObj) {
        Class<?>[] realIntfs = realObj.getClass().getInterfaces();
        Object proxyInstance = Proxy.newProxyInstance(MyRetryFactory.class.getClassLoader(), realIntfs,
                new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                // 接口方法上是否有@MyRetry
                if (method.isAnnotationPresent(MyRetry.class)) {
                    MyRetry myRetry = method.getDeclaredAnnotation(MyRetry.class);
                    int retryTimes = myRetry.retryTimes();
                    int[] retrySeconds = myRetry.retrySecond();

                    MyRetryTemplate myRetryTemplate = new MyRetryTemplate() {
                        @Override
                        public Object retry() throws Exception {
                            Object obj = method.invoke(realObj, args);
                            if (obj instanceof ResponseResult) {
                                // 网络异常,第三方接口也会返回结果,判断code是否等于0,决定是否重试
                                ResponseResult responseResult = (ResponseResult) obj;
                                if (responseResult == null || 	 (!"0".equals(responseResult.getCode()))) {
                                    if (responseResult == null) {
                                        throw new RuntimeException("接口返回对象为空");
                                    } else {
                                        throw new RuntimeException(responseResult.getMsg());
                                    }
                                }
                            }
                            return obj;
                        }
                    }.setRetryTimes(retryTimes).setRetrySeconds(retrySeconds);

                    // 先执行方法一次,再异步重试
                    try {
                        return myRetryTemplate.executeOnce();
                    } catch(Exception e) {
                        myRetryTemplate.executeAsync();
                    }
                    return null;
                } else {
                    return method.invoke(realObj, args);
                }
            };

        });

        return (T) proxyInstance;
    }
}
           

MyRetryTemplate

public abstract class MyRetryTemplate {

    private int retryTimes = 0;

    private int[] retrySeconds = {};

    public abstract Object retry() throws Exception;

    public Object executeOnce() throws Exception {
        System.out.println("第一次执行...");
        return retry();
    }

    public Object execute() {
        System.out.println("重试" + retryTimes + "次-分别相隔" + Arrays.toString(retrySeconds) + "秒");
        for (int i = 0; i < retryTimes; i++) {
            try {
                System.out.println(retrySeconds[i] + "s后准备第[" + (i + 1) + "]次重试!");
                Thread.sleep(1000 * retrySeconds[i]);
                return retry();
            } catch (Exception e) {
                System.out.println("重试失败:" + e);
            }
        }
        return null;
    }

    public void executeAsync() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                execute();
            }
        });
        thread.start();
    }

    public MyRetryTemplate setRetryTimes(int retryTimes) {
        this.retryTimes = retryTimes;
        return this;
    }

    public MyRetryTemplate setRetrySeconds(int[] retrySeconds) {
        this.retrySeconds = retrySeconds;
        return this;
    }
}
           

ResponseResult

private String code;
private String msg;
// 省略getter/setter方法
           

ThirdCallService

@MyRetry(retryTimes = 2, retrySecond = {3, 5})
ResponseResult push();
           

ThirdCallServiceImpl

@Override
public ResponseResult push() {
    System.out.println("push()");
    ResponseResult responseResult = new ResponseResult();
    responseResult.setCode("-1");
    responseResult.setMsg("连接超时,网络异常");
    return responseResult;
}
           

Tester

ThirdCallService thirdCallService = new ThirdCallServiceImpl();
ThirdCallService thirdCallServiceProxy = MyRetryFactory.getRetryServiceProxy(thirdCallService);
thirdCallServiceProxy.push();
System.out.println("=============================");
           

结果

第一次执行...
push()
=============================
重试2次-分别相隔[3, 5]秒
3s后准备第[1]次重试!
push()
重试失败:java.lang.RuntimeException: 连接超时,网络异常
5s后准备第[2]次重试!
push()
重试失败:java.lang.RuntimeException: 连接超时,网络异常
           

总结

1. 符合模板方法模式。因为要重试几次,所以需要循环,循环总体逻辑一致,但是要调用的外部接口有很多。
2. 第一次正常调用接口返回结果,失败重试n次采用异步调用。
3. 采用JDK的动态代理。调用每个外部接口时,统一交由代理类实现总体重试规则代码。
4. 接口标注注解,来区分外部接口是否需要重试。
   代理类可依照该注解,分别执行需要重试和不需要重试的逻辑,做到统一区分。
5. 外部接口一般异常(如网络异常)返回的对象也会有值(code,msg),code不为成功代号需要纳入重试机制。