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

小记

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

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

相关推荐
weixin_5450193235 分钟前
微信小程序智能商城系统(uniapp+Springboot后端+vue管理端)
spring boot·微信小程序·uni-app
一只码代码的章鱼2 小时前
Spring的 @Validate注解详细分析
前端·spring boot·算法
程序员小杰@3 小时前
【MCP教程系列】SpringBoot 搭建基于 Spring AI 的 SSE 模式 MCP 服务
人工智能·spring boot·spring
程序员buddha4 小时前
Spring & Spring Boot 常用注解整理
java·spring boot·spring
C_V_Better5 小时前
Java Spring Boot 控制器中处理用户数据详解
java·开发语言·spring boot·后端·spring
胡子洲5 小时前
Spring Boot 应用中实现基本的 SSE 功能
java·spring boot·后端
非著名架构师5 小时前
SpringBoot整合MQTT实战:基于EMQX构建高可靠物联网通信,从零到一实现设备云端双向对话
spring boot·mqtt·emqx
贰拾wan5 小时前
【Java-EE进阶】SpringBoot针对某个IP限流问题
java·spring boot·后端·idea
Paran-ia5 小时前
【2025版】Spring Boot面试题
java·spring boot·后端