如果有,请转给我!

1. 理解重试机制

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

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

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

重试机制要素

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

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

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

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

3. spring-retry重试组件

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

pom文件依赖

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

配置重试元数据

@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
回调方法执行!!!!

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

注解类:

/**
* 重试注解
*/
@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 : 重试失败,未进行任何补全,此为默认补全:打出错误日志

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

幂等性问题:

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

大量请求超时堆积:

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

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

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

所有业务类继承重试模板类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“并发送"重试机制"获取。

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

  1. 基于spring-boot和docker-java实现对docker容器的动态管理和监控[附完整源码下载]

    ​ (我是个封面) docker简介 Docker 是一个开源的应用容器引擎,和传统的虚拟机技术相比,Docker 容器性能开销极低,因此也广受开发者喜爱.随着基于docker的开发者越来越多,doc ...

  2. SpringBoot系列——状态机(附完整源码)

    1. 简单介绍状态机 2. 状态机的本质 3. 状态机应用场景 1. 简单介绍状态机 状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作.完成特定 ...

  3. (二、下) springBoot 、maven 、mysql、 mybatis、 通用Mapper、lombok 简单搭建例子 《附项目源码》

    接着上篇文章中 继续前进. 一.在maven 的pom.xm中添加组件依赖, mybatis通用Mapper,及分页插件 1.mybatis通用Mapper <!-- mybatis通用Mapp ...

  4. Shiro整合springboot,freemaker,redis(含权限系统完整源码)

    区块链技术联盟 2018-02-08 17:06:40 目录 一.导语 二.shiro功能介绍 三.shiro详解 四.shiro实战案例分享 五.系统配置 六.其他 一.导语 今天推荐给大家一个非常 ...

  5. 【陪你系列】5 千字长文+ 30 张图解 | 陪你手撕 STL 空间配置器源码

    大家好,我是小贺. 点赞再看,养成习惯 文章每周持续更新,可以微信搜索「herongwei」第一时间阅读和催更,本文 GitHub https://github.com/rongweihe/MoreT ...

  6. 鸿蒙内核源码分析(调度机制篇) | 任务是如何被调度执行的 | 百篇博客分析OpenHarmony源码 | v7.07

    百篇博客系列篇.本篇为: v07.xx 鸿蒙内核源码分析(调度机制篇) | 任务是如何被调度执行的 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调 ...

  7. JUC.Lock(锁机制)学习笔记[附详细源码解析]

    锁机制学习笔记 目录: CAS的意义 锁的一些基本原理 ReentrantLock的相关代码结构 两个重要的状态 I.AQS的state(int类型,32位) II.Node的waitStatus 获 ...

  8. [Android实例] Scroll原理-附ScrollView源码分析

    想象一下你拿着放大镜贴很近的看一副巨大的清明上河图, 那放大镜里可以看到的内容是很有限的, 而随着放大镜的上下左右移动,就可以看到不同的内容了 android中手机屏幕就相当于这个放大镜, 而看到的内 ...

  9. springboot aop 自定义注解方式实现一套完善的日志记录(完整源码)

    https://www.cnblogs.com/wenjunwei/p/9639909.html https://blog.csdn.net/tyrant_800/article/details/78 ...

随机推荐

  1. 一个poll的简单例子

    该程序使用poll事件机制实现了一个简单的消息回显的功能,其服务器端和客户端的代码如下所示: 服务器端: //start from the very beginning,and to create g ...

  2. 一个简单的wed服务器SHTTPD(2)———— 客户端请求分析

    //start from the very beginning,and to create greatness //@author: Chuangwei Lin //@E-mail:979951191 ...

  3. Dubbo(六):zookeeper注册中心的应用

    Dubbo中有一个非常本质和重要的功能,那就是服务的自动注册与发现,而这个功能是通过注册中心来实现的.而dubbo中考虑了外部许多的注册组件的实现,zk,redis,etcd,consul,eurek ...

  4. tp5中提示错误A non well formed numeric value encountered

    问题因为自动完成时间导致的 原来我的数据库是这样的 修改成下面这样就好了

  5. OpenWrt(LEDE)2020.4.12编译 UnPnP+NAS+多拨+网盘+DNS优化+帕斯沃 无缝集成

    固件说明 基于Lede OpenWrt R2020.4.8版本(源码截止2020.4.12)Lienol Feed及若干自行维护的软件包 结合家庭x86软路由场景需要定制 按照家庭应用场景对固件及软件 ...

  6. ubuntu 1604升级到ubuntu 1804无法忽视的细节问题(亲测有效)

    升级ubuntu系统,遇到很多问题,可能你在升级的时候也会碰到,希望对你有所帮助: 文章目录 1 常规升级过程 2 更改过源 3 无法全部更新 4 其他的问题 5 升级成功 6 无法进入gnome 6 ...

  7. CF#214 C. Dima and Salad 01背包变形

    C. Dima and Salad 题意 有n种水果,第i个水果有一个美味度ai和能量值bi,现在要选择部分水果做沙拉,假如此时选择了m个水果,要保证\(\frac{\sum_{i=1}^ma_i}{ ...

  8. Nginx|构建简单的文件服务器(mac) 续-FastDFS安装(mac)|文件存储方案

    目录 Nginx|构建简单的文件服务器(mac) 1 所需安装包 2 安装fastdfs-nginx-module-master 3 安装Nginx Nginx|构建简单的文件服务器(mac) 续上文 ...

  9. LeetCode 31. 下一个排列 | Python

    31. 下一个排列 题目 实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列. 如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列). 必须原地修改, ...

  10. 力扣题解-面试题22. 链表中倒数第K个节点

    题目描述 输入一个链表,输出该链表中倒数第k个节点.为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点.例如,一个链表有6个节点,从头节点开始,它们的值依次是1.2.3.4.5. ...