springboot系列——重试机制原理和应用,还有比这个讲的更好的吗(附完整源码)

简介:

springboot系列——重试机制原理和应用,还有比这个讲的更好的吗(附完整源码)

  1. 理解重试机制
  2. 总结重试机制使用场景
  3. spring-retry重试组件
  4. 手写一个基于注解的重试组件
  5. 重试机制下会出现的问题
  6. 模板方法设计模式实现异步重试机制
    如果有,请转给我!
  7. 理解重试机制

“重试是为了提高成功的可能性“

反过来理解,任何可能失败且允许重试操作的场景,就适合使用重试机制。但有了重试机制就一定能成功吗?显然不是。如果不成功就一直重试,这种处理方式会使得业务线程一直被重试占用,这样会导致服务的负载线程暴增直至服务宕机,因此需要限制重试次数。失败情况下,我们需要做后续的操作,如果是数据库操作的重试,需要回滚事物;如果是服务调用的重试,需要邮件报警通知运维开发人员,恢复服务。

对于服务接口调用,可能是因为网络波动导致超时失败,这时候所有重试次数是在很短时间内发起的话,就很容易全部超时失败,因此超时机制还需要引入重试动作之间时间间隔以及第一次失败后延迟多长时间再开始重试等机制。

重试机制要素

限制重试次数
每次重试的时间间隔
最终失败结果的报警或事物回滚
在特定失败异常事件情况下选择重试

  1. 总结重试机制使用场景

任何可能失败且允许重试操作的场景,就适合使用重试机制。那么在分布式系统开发环境中,哪些场景需要是使用重试机制呢。

乐观锁机制保证数据安全的数据更新场景,如账户信息的金额数据更新。
微服务的分布式架构下,服务的调用因超时而失败。

  1. spring-retry重试组件

spring-retry核心:配置重试元数据,失败恢复或报警通知。

pom文件依赖

<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>


配置重试元数据

@Override
@Retryable(value = Exception.class,maxAttempts = 3 , backoff = @Backoff(delay = 2000,multiplier = 1.5))
public int retryServiceOne(int code) throws Exception {

// TODO Auto-generated method stub 
System.out.println("retryServiceOne被调用,时间:"+LocalTime.now());
System.out.println("执行当前业务逻辑的线程名:"+Thread.currentThread().getName());
if (code==0){
    throw new Exception("业务执行失败情况!");
}
System.out.println("retryServiceOne执行成功!");

return 200;

}

配置元数据情况:

重试次数为3
第一次重试延迟2s
每次重试时间间隔是前一次1.5倍
Exception类异常情况下重试
测试:

启动应用,浏览器输入:http://localhost:8080/springRetry

后台结果:

执行业务发起逻辑的线程名:http-nio-8080-exec-6
retryServiceOne被调用,时间:17:55:48.235
执行当前业务逻辑的线程名:http-nio-8080-exec-6
retryServiceOne被调用,时间:17:55:50.235
执行当前业务逻辑的线程名:http-nio-8080-exec-6
retryServiceOne被调用,时间:17:55:53.236
执行当前业务逻辑的线程名:http-nio-8080-exec-6
回调方法执行!!!!

  1. 手写一个基于注解的重试组件

注解类:

/**

  • 重试注解
    */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface JdkRetry{


//默认
 int maxAttempts() default 3;
//默认每次间隔等待3000毫秒
 long waitTime() default 3000;

//捕捉到的异常类型  再进行重发
 Class<?> exception () default Exception.class ;

 String recoverServiceName () default "DefaultRecoverImpl";

}

注解类包含的元数据有:

尝试次数
重试间隔时间
抛出哪种异常会重试
重试完后还是失败的恢复类
使用spring AOP技术,实现重试注解的切面逻辑类RetryAspect。

@Transactional(rollbackFor = Exception.class)

  @Around("@annotation(jdkRetry)")
//开发自定义注解的时候,定要注意  @annotation(jdkRetry)和下面方法的参数,按规定是固定的形式的,否则报错
  public Object doConcurrentOperation(ProceedingJoinPoint pjp , JdkRetry jdkRetry) throws Throwable {
    //获取注解的属性

// pjp.getClass().getMethod(, parameterTypes)

    System.out.println("切面作用:"+jdkRetry.maxAttempts()+ "  恢复策略类:"+ jdkRetry.recoverServiceName());
    
    Object service = JdkApplicationContext.jdkApplicationContext.getBean(jdkRetry.recoverServiceName());
    Recover recover = null;
    if(service == null)
        return new Exception("recover处理服务实例不存在");
    recover = (Recover)service;
    
    long waitTime =  jdkRetry.waitTime();
    maxRetries = jdkRetry.maxAttempts();
    Class<?> exceptionClass = jdkRetry.exception();
    
    
    int numAttempts = 0;
    do {
        numAttempts++;
        try {
            //再次执行业务代码
            return pjp.proceed();
        } catch (Exception ex) {
            //必须只是乐观锁更新才能进行重试逻辑
            System.out.println(ex.getClass().getName());
            if(!ex.getClass().getName().equals(exceptionClass.getName()))
                throw ex;
            if (numAttempts > maxRetries) {
                
                recover.recover(null);
                //log failure information, and throw exception

// 如果大于 默认的重试机制 次数,我们这回就真正的抛出去了
// throw new Exception("重试逻辑执行完成,业务还是失败!");

            }else{
                //如果 没达到最大的重试次数,将再次执行
                System.out.println("=====正在重试====="+numAttempts+"次");
                TimeUnit.MILLISECONDS.sleep(waitTime);
            }
        }
    } while (numAttempts <= this.maxRetries);

    return 500;
  }

切面类获取到重试注解元信息后,切面逻辑会做以下相应的处理:

捕捉异常,对比该异常是否应该重试
统计重试次数,判断是否超限
重试多次后失败,执行失败恢复逻辑或报警通知
测试:

启动应用,浏览器输入:http://localhost:8080/testAnnotationRetry

结果:

切面作用:3 恢复策略类:DefaultRecoverImpl
AnnotationServiceImpl被调用,时间:18:11:25.748
org.jackdking.retry.jdkdkingannotation.retryException.UpdateRetryException
=====正在重试=====1次
AnnotationServiceImpl被调用,时间:18:11:28.748
org.jackdking.retry.jdkdkingannotation.retryException.UpdateRetryException
=====正在重试=====2次
AnnotationServiceImpl被调用,时间:18:11:31.749
org.jackdking.retry.jdkdkingannotation.retryException.UpdateRetryException
=====正在重试=====3次
AnnotationServiceImpl被调用,时间:18:11:34.749
org.jackdking.retry.jdkdkingannotation.retryException.UpdateRetryException
2020-05-26 18:11:34.749 ERROR 14892 --- [io-8080-exec-10] o.j.r.j.recover.impl.DefaultRecoverImpl : 重试失败,未进行任何补全,此为默认补全:打出错误日志

  1. 重试机制下会出现的问题

幂等性问题:

在分布式架构下,服务之间调用会因为网络原因出现超时失败情况,而重试机制会重复多次调用服务,但是对于被调用放,就可能收到了多次调用。如果被调用方不具有天生的幂等性,那就需要增加服务调用的判重模块,并对每次调用都添加一个唯一的id。

大量请求超时堆积:

超高并发下,大量的请求如果都进行超时重试的话,如果你的重试时间设置不安全的话,会导致大量的请求占用服务器线程进行重试,这时候服务器线程负载就会暴增,导致服务器宕机。对于这种超高并发下的重试设计,我们不能让重试放在业务线程,而是统一由异步任务来执行。

  1. 模板方法设计模式实现异步重试机制

模板方法设计模式来实现异步重试机制

所有业务类继承重试模板类RetryTemplate

@Service("serviceone")
public class RetryTemplateImpl extends RetryTemplate{


public RetryTemplateImpl() {
    // TODO Auto-generated constructor stub
    this.setRecover(new RecoverImpl());
}

@Override
protected Object doBiz() throws Exception {
    // TODO Auto-generated method stub
    int code = 0;
    System.out.println("RetryTemplateImpl被调用,时间:"+LocalTime.now());
    if (code==0){
        throw new Exception("业务执行失败情况!");
    }
    System.out.println("RetryTemplateImpl执行成功!");

    return 200;
}

class RecoverImpl implements Recover{

    @Override
    public String recover() {
        // TODO Auto-generated method stub
        System.out.println("重试失败   恢复逻辑,记录日志等操作");
        return null;
    }
}

}

业务实现类在doBiz方法内实现业务过程
所有业务实现一个恢复类,实现Recover接口,重试多次失败后执行恢复逻辑
测试:

启动应用,浏览器输入:http://localhost:8080/testRetryTemplate

结果:

2020-05-26 22:53:41.935 INFO 25208 --- [nio-8080-exec-4] o.j.r.r.c.RetryTemplateController : 开始执行业务
RetryTemplateImpl被调用,时间:22:53:41.936
RetryTemplateImpl被调用,时间:22:53:41.938
RetryTemplateImpl被调用,时间:22:53:44.939
RetryTemplateImpl被调用,时间:22:53:47.939
2020-05-26 22:53:50.940 INFO 25208 --- [pool-1-thread-1] o.j.r.r.service.RetryTemplate : 业务逻辑失败,重试结束
重试失败 恢复逻辑,记录日志等操作

完整的demo项目,请关注公众号“前沿科技bot“并发送"重试机制"获取。

原文地址https://www.cnblogs.com/HelloWorld2016425/p/12984737.html

相关文章
|
5天前
|
Web App开发 编解码 Java
B/S基层卫生健康云HIS医院管理系统源码 SaaS模式 、Springboot框架
基层卫生健康云HIS系统采用云端SaaS服务的方式提供,使用用户通过浏览器即能访问,无需关注系统的部署、维护、升级等问题,系统充分考虑了模板化、配置化、智能化、扩展化等设计方法,覆盖了基层医疗机构的主要工作流程,能够与监管系统有序对接,并能满足未来系统扩展的需要。
33 4
|
24天前
|
缓存 前端开发 Java
【Java】仓库管理系统 SpringBoot+LayUI+DTree(源码)【独一无二】
【Java】仓库管理系统 SpringBoot+LayUI+DTree(源码)【独一无二】
|
1天前
|
Java Spring 容器
SpringBoot自动装配原理之@Import注解解析
SpringBoot自动装配原理之@Import注解解析
|
4天前
|
JSON Java Maven
Javaweb之SpringBootWeb案例之 SpringBoot原理的详细解析
Javaweb之SpringBootWeb案例之 SpringBoot原理的详细解析
8 0
Javaweb之SpringBootWeb案例之 SpringBoot原理的详细解析
|
8天前
|
人工智能 移动开发 前端开发
Springboot医院智慧导诊系统源码:精准推荐科室
医院智慧导诊系统是在医疗中使用的引导患者自助就诊挂号,在就诊的过程中有许多患者不知道需要挂什么号,要看什么病,通过智慧导诊系统,可输入自身疾病的症状表现,或选择身体部位,在经由智慧导诊系统多维度计算,精准推荐科室,引导患者挂号就诊,实现科学就诊,不用担心挂错号。
17 2
|
8天前
|
人工智能 前端开发 Java
Java语言开发的AI智慧导诊系统源码springboot+redis 3D互联网智导诊系统源码
智慧导诊解决盲目就诊问题,减轻分诊工作压力。降低挂错号比例,优化就诊流程,有效提高线上线下医疗机构接诊效率。可通过人体画像选择症状部位,了解对应病症信息和推荐就医科室。
148 10
|
8天前
|
Java 关系型数据库 MySQL
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
UWB (ULTRA WIDE BAND, UWB) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。一套UWB精确定位系统,最高定位精度可达10cm,具有高精度,高动态,高容量,低功耗的应用。
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
|
9天前
|
存储 数据可视化 安全
Java全套智慧校园系统源码springboot+elmentui +Quartz可视化校园管理平台系统源码 建设智慧校园的5大关键技术
智慧校园指的是以物联网为基础的智慧化的校园工作、学习和生活一体化环境,这个一体化环境以各种应用服务系统为载体,将教学、科研、管理和校园生活进行充分融合。无处不在的网络学习、融合创新的网络科研、透明高效的校务治理、丰富多彩的校园文化、方便周到的校园生活。简而言之,“要做一个安全、稳定、环保、节能的校园。
35 6
|
12天前
|
消息中间件 运维 供应链
springboot区域云HIS医院信息综合管理平台源码
云HIS系统分为两个大的系统,一个是基层卫生健康云综合管理系统,另一个是基层卫生健康云业务系统。基层卫生健康云综合管理系统由运营商、开发商和监管机构使用,用来进行运营管理、运维管理和综合监管。基层卫生健康云业务系统由基层医院使用,用来支撑医院各类业务运转。
21 2
|
17天前
|
Java 容器 Spring
Springboot自动配置原理
Springboot自动配置原理