Springboot实现重试机制

背景

研发工作中时常遇到要和其他服务对接,依赖对方能力的情况,最恶心的是对方提供的服务不稳定,时灵时不灵的,进而影响到自己功能的稳定性。万一发生了这种事,做为研发,咱该怎么办?通过容错直接抛出异常,让用户再试一次?那多low啊!一个优秀的研发很少将问题抛出去,一般都是自己尝试多遍且没办法之后,才会选择将问题反馈给用户。这就要求咱们的相关功能得有重试的能力。今天雷袭实践的课题就是在Springboot项目中实现接口自动重试机制。

代码实践

一、 纯手撸的重试机制

雷袭看到这个课题,第一时间想的是:"这么简单,还需要水个博客?写个try catch加while循环,不是分分钟解决问题吗? 以下是我的第一版代码:

java 复制代码
    public <T> T retryMethodLocal(Supplier<T> method, int maxRetries) {
        int retryCount = 0;
        while (retryCount <= maxRetries) {
            try {
                // 尝试执行方法
                return method.get();
            } catch (Exception e) {
                retryCount++;
                if (retryCount > maxRetries) {
                    // 超过最大重试次数后抛出异常
                    throw e;
                }
                System.out.println("第 " + retryCount + " 次重试...");
            }
        }
        return null; // 理论上不会到达这里
    }


    //测试方法,用于模拟一个不稳定的接口。当随机小数小于0.8时,抛错,
    public String testMethod(){
        StringBuilder builder = new StringBuilder("进入到testMethod方法");
        if (date != null){
            builder.append(",与上一次的时间间隔为:" + ( new Date().getTime()-date.getTime()));
        }
        date = new Date();
        log.info(builder.toString());

        // 示例:随机失败
        double random = Math.random();
        if (random < 0.8) {
            log.error("调用失败 " + random);
            throw new RuntimeException("调用失败 " + random);
        }
        log.error("调用成功 " + random);
        return "成功";
    }

Controller方法如下:

java 复制代码
    @GetMapping("/retryOne")
    public Object retryOne() {
        return leixiRetryService.retryMethodLocal(leixiRetryService::testMethod, 5);
    }

通过浏览器访问链接:http://127.0.0.1:19200/leixi/retryOne , 以下是控制台信息:

根据控制台的日志可知,重试机制是生效的。

二、通过guava-retrying 实现重试

以上方法虽然能解决问题,但是它太原生了。像重试这样的基础机制,Springboot肯定提供了相关的能力或组件,咱们完全不用自己写。在咨询了同事后,我又剽来了一种常用的方法,实现如下:

pom.xml增加依赖:

XML 复制代码
       <dependency>
            <groupId>com.github.rholder</groupId>
            <artifactId>guava-retrying</artifactId>
            <version>2.0.0</version>
        </dependency>

service层添加方法:

java 复制代码
    public <T> T retryMethodByRetryer(Supplier<T> method, Integer maxRetries, Integer waitTime) {
        Retryer<T> retryer = RetryerBuilder.<T>newBuilder()
                //.retryIfResult(result -> !result.equals("成功"))   //还可以通过返回结果判定是否重试
                .retryIfException()
                .withWaitStrategy(WaitStrategies.fixedWait(waitTime, TimeUnit.MILLISECONDS))
                .withStopStrategy(StopStrategies.stopAfterAttempt(maxRetries)).withRetryListener(new RetryListener() {
                    @Override
                    public <V> void onRetry(Attempt<V> attempt) {
                        log.info("第【{}】次重试,距离首次调用已过【{}】ms", attempt.getAttemptNumber(),
                                attempt.getDelaySinceFirstAttempt());
                    }
                }).build();
        try {
            return retryer.call(method::get);
        } catch (RetryException | ExecutionException e) {
            log.error(">>> 重试多次,远程获取批量报告文档url失败 <<<", e);
        }
        return null ;
    }

Controller层增加方法:

java 复制代码
    @GetMapping("/retryTwo")
    public Object retryTwo() {
        return leixiRetryService.retryMethodByRetryer(() -> leixiRetryService.testMethod(), 5, 100);
    }

通过浏览器访问链接:http://127.0.0.1:19200/leixi/retryTwo , 以下是控制台信息:

该方法相比于第一版就优雅了很多,它通过封装的重试对象,可以精准的捕获到调用的次数,间隔时间,调用结果,还可以手动设置调用间隔,对于触发调用的条件也更加灵活。

三、 通过spring-retry实现重试

除了上述方法,Spring 还提供了一种重试策略,通过注解来实现,代码如下:

添加pom依赖:

XML 复制代码
        <!-- 重试 -->
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>

Application.java中添加重试注解:@EnableRetry

Service层添加方法:

XML 复制代码
    @Retryable(maxAttempts = 5, backoff = @Backoff(value = 100L, multiplier = 1.5))
    public String retryByTarget() {
        return testMethod();
    }

Controller中增加方法:

java 复制代码
    @GetMapping("/retryThree")
    public Object retryThree() {
        return leixiRetryService.retryByTarget();
    }

通过浏览器访问链接:http://127.0.0.1:19200/leixi/retryThree , 以下是控制台信息:

相比于前两种方式,这种方法更加优雅,代码量更少,且通过注解的方式,可以很容易的对功能进行大批量的修改。请各位留意@Retryable中的@Backoff注解,它控制接口调用的间隔时间梯度递增,如第一次间隔100ms,第二次间隔100*1.5ms,第三次间隔100*1.5*1.5,以此类推,如此很好的避免了有故障的服务在短时间内接到大量重试调用,从而导致问题恶化的情况。

小记

要注意的是,配置了重试机制的功能,需要保证它在事务上是幂等的。即无论第一次执行成功与否,再执行第二次时,其结果都不会改变。多次执行的结果是一致的。

雷袭很喜欢在工作中捕捉到能令自己惊艳的东西,比较各种工具/方法的不同实现方式,以增长见闻,提升眼界。每每发现到自己又有新的发现,总是欣喜莫名,程序员的乐趣,不外如是。

相关推荐
paopaokaka_luck21 分钟前
基于SpringBoot+Vue的电影售票系统(协同过滤算法)
vue.js·spring boot·后端
陌殇殇3 小时前
SpringBoot整合SpringCache缓存
spring boot·redis·缓存
小林学习编程6 小时前
Springboot + vue + uni-app小程序web端全套家具商场
前端·vue.js·spring boot
ladymorgana6 小时前
【Spring boot】tomcat Jetty Undertow对比,以及应用场景
spring boot·tomcat·jetty
IT_10246 小时前
Spring Boot项目开发实战销售管理系统——系统设计!
大数据·spring boot·后端
DCTANT7 小时前
【原创】国产化适配-全量迁移MySQL数据到OpenGauss数据库
java·数据库·spring boot·mysql·opengauss
Touper.7 小时前
SpringBoot -- 自动配置原理
java·spring boot·后端
喜欢敲代码的程序员9 小时前
SpringBoot+Mybatis+MySQL+Vue+ElementUI前后端分离版:项目搭建(一)
spring boot·mysql·elementui·vue·mybatis
华子w90892585910 小时前
基于 SpringBoot+Vue.js+ElementUI 的 “花开富贵“ 花园管理系统设计与实现7000字论文
vue.js·spring boot·elementui
小时候的阳光11 小时前
SpringBoot3 spring.factories 自动配置功能不生效?
spring boot·spring·失效·factories·imports