天天看點

外部接口調用失敗重試

文章目錄

    • @[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不為成功代号需要納入重試機制。