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,以此类推,如此很好的避免了有故障的服务在短时间内接到大量重试调用,从而导致问题恶化的情况。

小记

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

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

相关推荐
MacroZheng1 小时前
还在用WebSocket实现即时通讯?试试MQTT吧,真香!
java·spring boot·后端
midsummer_woo1 小时前
基于springboot的IT技术交流和分享平台的设计与实现(源码+论文)
java·spring boot·后端
别惹CC3 小时前
Spring AI 进阶之路01:三步将 AI 整合进 Spring Boot
人工智能·spring boot·spring
柯南二号4 小时前
【Java后端】Spring Boot 集成 MyBatis-Plus 全攻略
java·spring boot·mybatis
javachen__5 小时前
SpringBoot整合P6Spy实现全链路SQL监控
spring boot·后端·sql
IT毕设实战小研11 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
一只爱撸猫的程序猿12 小时前
使用Spring AI配合MCP(Model Context Protocol)构建一个"智能代码审查助手"
spring boot·aigc·ai编程
甄超锋13 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
武昌库里写JAVA15 小时前
JAVA面试汇总(四)JVM(一)
java·vue.js·spring boot·sql·学习
Pitayafruit16 小时前
Spring AI 进阶之路03:集成RAG构建高效知识库
spring boot·后端·llm