java接口异常重试机制实现(guava-retrying方式)

目录

需求背景

解决思路

方法一

方法二

方法三

解决案例

1. pom引用

2. 举个栗子

3. 实战代码梳理

4. 结语


需求背景

接到需求如下:超时处理机制,若调用xx风控中台服务后,在指定超时时间内,未收到应答,则平台需要再次调用xx风控中台服务,各风险侦测服务的具体超时时间,参见各接口规范balabalabala

解决思路

上面需求明确指出,如接口调用超时,需要再次发起,即重试。

java的重试:

方法一

最原始的for或者while就可以。不用多说,最初级的攻城狮应该都能写出来的,对吧?

方法二

据了解,spring有注解,可通过spring-retry注解@Retryable来实现。再了解,发现这个注解的条件比较苛刻。需要在@Configuration注解的类中添加@EnableRetry,最后在想要重试的方法上添加@Retryable(Exception.class)由于retry用到了aspect增强,所有会有aspect的坑,就是方法内部调用,会使aspect增强失效,那么retry当然也会失效。也就说,方法体内调用是不行的,而且一个类中只能有一个@Retryable。方法中不支持throw异常信息等,不支持返回(说法不够严谨),不够灵活。

方法三

guava-retrying,是一个java解决重试的工具包。此方式更加灵活,便于提取公共方法类,低耦合,可以返回和支持方法中throw异常。方法写了,提供给新手使用不是更好吗?

解决案例

1. pom引用

guava-retrying项目地址https://github.com/rholder/guava-retrying。


        
            com.github.rholder
            guava-retrying
            2.0.0
        

2. 举个栗子

import com.github.rholder.retry.*;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * @Author : whb
 * @Description : guava重试demo
 */
@Slf4j
public class GuavaRetryDemo {
    public static void main(String[] args) {
        //定义请求实现
        Callable callable = () -> {
            // do something useful here
            log.info("重试了一次");
            throw new RuntimeException();
        };

        //定义重试机制
        Retryer retryer = RetryerBuilder.newBuilder()
                // retryIf 重试条件
                .retryIfResult(Objects::isNull)
                //设置异常重试源
                .retryIfExceptionOfType(IOException.class)
                .retryIfRuntimeException()
                .retryIfResult(res -> res = false)
                //设置等待间隔时间
                .withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS))
                //设置最大重试次数
                .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                .build();
        try {
            retryer.call(callable);
        } catch (RetryException | ExecutionException e) {
            e.printStackTrace();
        }

    }

3. 实战代码梳理

a. 提取重试策略类

import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
import com.google.common.base.Predicates;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.net.SocketTimeoutException;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName UpRiskClientRetryer
 * 风控查询,超时后重试策略
 * @Author wuhebin
 * @Date 2022/4/26 9:16
 * @Version 1.0
 **/
@Slf4j
@Component
public class UpRiskClientRetryer {
    //定义通用的重试器
    private Retryer defaultRetryer;
    //定义其他的重试器
    private Retryer millRetryer;

    /**
     * @Author wuhebin
     * 重试间隔时长20s
     * @Date 2022/4/26 10:24
     **/
    @Bean(value = "defaultRetryer")
    public Retryer defaultRetryer(){
        log.info("===========init defaultRetryer==========");
        defaultRetryer = RetryerBuilder.newBuilder()
                //retryIf 重试条件
                //retryIfException,抛出 runtime 异常、checked 异常时都会重试,但是抛出 error 不会重试。
                //.retryIfException()
                //retryIfRuntimeException 只会在抛 runtime 异常的时候才重试,checked 异常和error 都不重试。
                //.retryIfRuntimeException()
                //retryIfExceptionOfType 允许我们只在发生特定异常的时候才重试,比如NullPointerException 和 IllegalStateException 都属于 runtime 异常,也包括自定义的error。
                .retryIfExceptionOfType(SocketTimeoutException.class)
//                .retryIfException(Predicates.equalTo(new Exception()))
                //retryIfResult 可以指定你的 Callable 方法在返回值的时候进行重试,如 返回false时重试
//                .retryIfResult(Predicates.equalTo(false))
                //等待策略:每次请求间隔1s
                .withWaitStrategy(WaitStrategies.fixedWait(20, TimeUnit.SECONDS))
                //停止策略 : 尝试请求3次
                .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                .build();
        return defaultRetryer;
    }

    /**
     * @Author wuhebin
     * 重试间隔时长2s
     * @Date 2022/4/26 10:24
     **/
    @Bean(value = "millRetryer")
    public Retryer millRetryer(){
        log.info("===========init millRetryer==========");
        millRetryer = RetryerBuilder.newBuilder()
                //retryIf 重试条件
                .retryIfExceptionOfType(SocketTimeoutException.class)
                //等待策略:每次请求间隔1s
                .withWaitStrategy(WaitStrategies.fixedWait(2, TimeUnit.SECONDS))
                //停止策略 : 尝试请求3次
                .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                .build();
        return millRetryer;
    }
}

b. 其他类调用

defaultRetryer.call(myServiceCallable)

c. 提取重试方法

    @Autowired
    private Retryer defaultRetryer;
    @Autowired
    private Retryer millRetryer;


/**
     * @Author wuhebin
     * 超时重试策略
     * @Date 2022/4/26 10:09
     * @Param
     * @return
     **/
    private JSONObject doRetry(String url , int milliseconds,RiskQueryHeaderVo headerVo, JSONObject inMessage){
        Callable myServiceCallable = new Callable() {
            int times = 1;
            @Override
            public JSONObject call() throws Exception{
                log.info("地址:"+url+",请求超时,重试了{}次", times);
                return doRiskClientService(url, milliseconds, headerVo, inMessage);
            }
        };

        try {
            //黑白名单的重试间隔是2s,其他是20s
            if(milliseconds==RiskConstant.R_CARD_BLACK_LIST_TIMEOUT){
                return (JSONObject)millRetryer.call(myServiceCallable);
            }
            return (JSONObject)defaultRetryer.call(myServiceCallable);
        } catch (ExecutionException e) {
            e.printStackTrace();
            return null;
        } catch (RetryException e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * @Author wuhebin
     * 风控接口服务
     * @Date 2022/4/20 9:51
     * @Param url 地址
     * @Param milliseconds 超时毫秒
     * @Param headerVo 一些头部参数
     * @Param inMessage 主体报文内容
     * @return
     **/
    public JSONObject doRiskClientService(String url , int milliseconds,RiskQueryHeaderVo headerVo, JSONObject inMessage) throws Exception{

//        throw new SocketTimeoutException();//测试超时重试
        JSONObject respObj = JSONUtil.parseObj("{\"msg\":\"请求业务成功\",\"code\":\"RT1000\",\"subMsg\":\"\",\"subCode\":\"\",\"data\":{\"C401\":\"20\",\"C402\":\"5\",\"C403\":\"12\"}}");
        .......HttpRequest.post......

        return respObj;
}

d. 使用

public RiskQueryRespVo doQueryPersonVfyService(String reqId, JSONObject inMessage) {
     .......
     JSONObject respObj = doRetry(url,RiskConstant.R_URP_TIMEOUT,headerVo,inMessage);
     ......
}

4. 结语

guava-retrying也存在弊端,代码不够优雅,相比spring-retry注解方式,代码显得更多,但是个人认为方便提取公共类给小白使用的工具才是好的工具,不一定要优雅。

你可能感兴趣的:(java,java,spring,接口异常重试)