首页 > 其他分享 >spring retry

spring retry

时间:2023-08-25 17:45:48浏览次数:46  
标签:retry spring 重试 RetryContext context new public

一、接入

spring boot 2.7.14

spring retry 从2.0.2版本之后,从spring batch里剥离出来成为一个单独的工程,因此我们引入spring retry最新版本可以直接如下引入

<dependency>
   <groupId>org.springframework.retry</groupId>
   <artifactId>spring-retry</artifactId>
   <version>2.0.2</version>
</dependency>

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

启动类上打上注解@EnableRetry
image.png

二、使用注解

Spring retry作为重试组件,可以直接使用@Retryable注解;废话不多说,直接上代码

@Component
public class UserService {


    @Retryable(retryFor = BizException.class, maxAttempts = 5, backoff = @Backoff(delay = 1000L, multiplier=1))
    public int service(int throwErr) throws BizException {

        System.out.println("===== service =====" + DateUtil.getDateTime(new Date()));

        if (throwErr==1) {
            throw new BizException();
        }

        return 1;
    }
}

执行效果如下

image.png

retryFor指定异常进行重试,如果不指定的话,默认任何异常都会重试;
maxAttempts重试次数,默认值是3次;
backoff是用于控制延迟重试策略,@Backoff(delay = 1000L, multiplier=2)表示每次执行失败,再次延迟时间=上次延迟时间*2

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {
    //最终失败情况下,调用recover进行恢复
    String recover() default "";

    //拦截器,主要是aop切面逻辑,具体待验证, 目前看下来主要是配合stateful一起使用,带验证
    String interceptor() default "";


    //包含哪些异常
    @AliasFor("include")
    Class<? extends Throwable>[] retryFor() default {};

    //哪些异常不重试
    @AliasFor("exclude")
    Class<? extends Throwable>[] noRetryFor() default {};

    //哪些异常不作回滚
    Class<? extends Throwable>[] notRecoverable() default {};

    //标签,没啥意义
    String label() default "";
   
    //有状态的重试,这个我们单独开讲
    boolean stateful() default false;

    //最大重试次数
    int maxAttempts() default 3;
    
    //最大重试次数表达式
    String maxAttemptsExpression() default "";

    //延迟策略
    Backoff backoff() default @Backoff;

    //异常过滤
    String exceptionExpression() default "";
    
    //监听器
    String[] listeners() default {};
}

三、使用RetryTemplate

上面通过注解@Retryable(retryFor = BizException.class, maxAttempts = 5, backoff = @Backoff(delay = 1000L, multiplier=1)) 的效果,可以通过如下编码的形式来实现

public int service2(int throwErr) throws BizException {
    RetryTemplate template = RetryTemplate.builder()
            .maxAttempts(3)
            .retryOn(BizException.class)
            .customBackoff(
                    BackOffPolicyBuilder
                            .newBuilder()
                            .delay(1000)
                            .multiplier(2)
                            .build()

            )
            .build();

    return template.execute(ctx->{
        System.out.println("===== service =====" + DateUtil.getDateTime(new Date()));

        if (throwErr==1) {
            throw new BizException("system error");
        }

        return 1;
    });
}

四、关于RetryContentxt

普通场景下,重试是不需要获取之前重试的状态的,但是某些场景下,每次重试可能都需要打印当前重试次数,并且塞进去相关信息等

template.execute(ctx->{
    System.out.println("===== service =====" + DateUtil.getDateTime(new Date()));
    //上下文获取当前重试次数
    System.out.println("retry count="+ctx.getRetryCount());
    //也可以塞一些东西进去
    System.out.println("retry count="+ctx.getAttribute("attr"))
    //从属性里取值
    ctx.setAttribute("attr", "test"+ctx.getRetryCount());
    
    //直接终止当前重试,这个比较牛逼,配合固定重试次数来搞
    ctx.setExhaustedOnly()

    if (throwErr==1) {
        throw new BizException("system error");
    }

    return 1;
});

五、重试策略&延迟重试

我们直接把重试策略

public interface RetryPolicy extends Serializable {

   /**
    * @param context the current retry status
    * @return true if the operation can proceed
    */
   boolean canRetry(RetryContext context);

   /**
    * Acquire resources needed for the retry operation. The callback is passed in so that
    * marker interfaces can be used and a manager can collaborate with the callback to
    * set up some state in the status token.
    * @param parent the parent context if we are in a nested retry.
    * @return a {@link RetryContext} object specific to this policy.
    *
    */
   RetryContext open(RetryContext parent);

   /**
    * @param context a retry status created by the {@link #open(RetryContext)} method of
    * this policy.
    */
   void close(RetryContext context);

   /**
    * Called once per retry attempt, after the callback fails.
    * @param context the current status object.
    * @param throwable the exception to throw
    */
   void registerThrowable(RetryContext context, Throwable throwable);

}

image.png
从上图我们可以看到很多重试策略的实现,

六、recover

retry组件重试最终失败后,会调用recover方法(有点像回滚)

@Component
public class TestService {

    @Retryable(retryFor = RemoteAccessException.class)
    public void service() {
        System.out.println("===== service =====" + DateUtil.getDateTime(new Date()));
        throw new RemoteAccessException("xx");
    }
    @Recover
    public void recover(RemoteAccessException e) {
        System.out.println("===== recover =====" + DateUtil.getDateTime(new Date()));

    }

}

image.png

七、监听器Listeners

参考接口:

public interface RetryListener {

    void open(RetryContext context, RetryCallback<T> callback);

    void onSuccess(RetryContext context, T result);

    void one rror(RetryContext context, RetryCallback<T> callback, Throwable e);

    void close(RetryContext context, RetryCallback<T> callback, Throwable e);

}

实现如下:

@Bean("listener1")
public RetryListener getListener() {
    return new RetryListener() {
        @Override
        public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {

            System.out.println("===== close =====");
        }

        @Override
        public <T, E extends Throwable> void onSuccess(RetryContext context, RetryCallback<T, E> callback, T result) {
            System.out.println("===== close =====");
        }
    };
}

然后在注解上引用

@Retryable(retryFor = RemoteAccessException.class, maxAttempts = 4,
        backoff = @Backoff(delay = 1000L, multiplier=2),
        listeners = {"listener1"}
)
public void service() {
    System.out.println("===== service =====" + DateUtil.getDateTime(new Date()));
    throw new RemoteAccessException("xx");
}

最终执行效果如下

image.png

八、有状态的重试stateful

有状态重试通常是用在message-driven 的应用中,从消息中间件比如RabbitMQ等接收到的消息,如果应用处理失败,那么消息中间件服务器会再次投递,再次投递时,对于集成了Spring Retry的应用来说,再次处理之前处理失败的消息,就是一次重试;也就是说,Spring Retry能够识别出,当前正在处理的消息是否是之前处理失败过的消息;

如果是之前处理过的消息,Spring Retry就可以使用 back off policies 阻塞当前线程;Spring Retry同时追踪重试的次数,支持处理彻底失败后的recover,这也是使用有状态重试的理由;

有状态重试的另一个典型应用场景是跟Spring Transaction框架集成。在集成了Spring Transaction框架的MVC应用中,通过TransactionInterceptor,开启对Service层的事务管理;在这种情况下,Spring Retry会提供让每一次重试和重试次数耗尽之后的recover都在一个新的事务中执行。

九、retry组件的忧缺点

整个使用下来,retry组件的优点:

  1. 无侵入式的实现了重试,大大减小了重试代码成本
  2. 重试策略比较灵活,支持固定频率重试、延迟重试等策略

缺点:

  1. 不支持异步重试,且重试过程是阻塞当前程序的,当然,如果要实现异步重试,需要配合@Async注解来搞

标签:retry,spring,重试,RetryContext,context,new,public
From: https://www.cnblogs.com/viogs/p/17657582.html

相关文章

  • Spring 常用注解
    今天给大家介绍一下Spring中的常用注解,同时这些注解也是Spring中经常用到的注解,下面我们就一起来看看都有哪些注解吧。1.@Controller在控制层使用,标识该类是SpringMVCcontroller处理器,用来创建处理http请求的对象。2.@Service在业务逻辑层使用,用于标注业务层组件。......
  • 在 IDEA 中创建 Spring Boot 项目的方式
    点击左边的SpringInitializr(https://start.spring.io),默认选择你的JDK和构建SpringBoot项目的URL,接着点击右下角的Next尝试阿里云提供的脚手架https://start.aliyun.com,选择Custom,将阿里云的URL复制过去。两个地址官方:https://start.spring.io阿里的:http......
  • Mongodb数据库基于spring-boot-starter-data-mongodb的查询工具
    /***字段注解*/public@interfaceBuilderField{/***对应的数据库字段名称*@return*/Stringname();}importlombok.SneakyThrows;importjava.io.Serializable;importjava.lang.invoke.SerializedLambda;importjava.lang.reflec......
  • 【解决】idea启动spring MVC报错:一个或多个listeners启动失败Listener ClassNotFoundE
    idea配置教程。tomcat调试报错Artifact:warexploded:Errorduringartifactdeployment。修改代码后,启动不生效,仍是旧代码。根本原因是:Modulesoutputpath和Artifactsoutputdirectory不匹配Modulesoutputpath一定要等于Artifactsoutputdirectory加\WEB-INF\classes......
  • springboot结合baomidou dynamic-datasource组件实现多数据源
    当系统数据量过大,系统性能问题逐渐浮出水面。使用主从模式,不失是一个较好的选择。即业务在主库执行,不影响业务的查询考虑走从库。这时,程序需要动态多数据源配置。......
  • SpringBoot配置加载方式
    在开发中,我们经常会和配置打交道,SpringBoot为了方便配置的的管理和加载,提供了灵活的配置文件加载机制。它允许从多个来源中加载应用配置信息。如系统默认配置、外部配置文件、环境变量、命令行参数、数据库、配置中心等。下面介绍几种常见的属性来源配置方式。命令行参数Spring......
  • springboot整合redis回滚
    1:添加依赖2:yml中配置连接,如:host,password,port3:@autowired注解注入Redistemplate调用方法生成对象 为了方便公司开发,一般会对数据进行序列化存储,这时需要创建配置类进行全局设置packagecom.example.config;importcom.fasterxml.jackson.annotation.JsonAutoDetect;importco......
  • Spring加载机制的设计与实现
    骑士李四记录:1.ApplicationContext是什么ApplicationContext是Spring上下文的核心接口,描述了Spring容器的所有基本功能,是SpringContext(Spring上下文)模块的核心设计。想了解Spring的加载机制,则必须先明白SpringApplicationContext(后简称Spring上下文)到底是什么、是怎么设计的、......
  • Spring框架源码结构
    骑士李四记录:Spring源码解析Spring是一款用于简化企业级Java应用开发的分层开源框架,它有着强大的扩展、融合能力,善于将各种单层框架完美地糅合在一起,并建立一个完整体系,统一、高效地构造可提供企业级服务的应用系统。Spring主要分为8大模块:数据处理模块(DataAccess/Integration)、......
  • 基于springboot城镇保障性住房管理系统
    随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了城镇保障性住房管理系统的开发全过程。通过分析城镇保障性住房管理系统管理的不足,创建了一个计算机管理城镇保障性住房管理系统的方案。文章介绍了城镇保障性住房管理系统的系统分析部......