1. 理解重试机制spring
6. 模板方法设计模式实现异步重试机制springboot
若是有,请转给我!服务器
“重试是为了提升成功的可能性“架构
反过来理解,任何可能失败且容许重试操做的场景,就适合使用重试机制。但有了重试机制就必定能成功吗?显然不是。若是不成功就一直重试,这种处理方式会使得业务线程一直被重试占用,这样会致使服务的负载线程暴增直至服务宕机,所以须要限制重试次数。失败状况下,咱们须要作后续的操做,若是是数据库操做的重试,须要回滚事物;若是是服务调用的重试,须要邮件报警通知运维开发人员,恢复服务。并发
对于服务接口调用,多是由于网络波动致使超时失败,这时候全部重试次数是在很短期内发起的话,就很容易所有超时失败,所以超时机制还须要引入重试动做之间时间间隔以及第一次失败后延迟多长时间再开始重试等机制。
重试机制要素
任何可能失败且容许重试操做的场景,就适合使用重试机制。那么在分布式系统开发环境中,哪些场景须要是使用重试机制呢。
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; }
配置元数据状况:
测试:
启动应用,浏览器输入: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 回调方法执行!!!!
注解类:
/** * 重试注解 */ @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 : 重试失败,未进行任何补全,此为默认补全:打出错误日志
幂等性问题:
在分布式架构下,服务之间调用会由于网络缘由出现超时失败状况,而重试机制会重复屡次调用服务,可是对于被调用放,就可能收到了屡次调用。若是被调用方不具备天生的幂等性,那就须要增长服务调用的判重模块,并对每次调用都添加一个惟一的id。
大量请求超时堆积:
超高并发下,大量的请求若是都进行超时重试的话,若是你的重试时间设置不安全的话,会致使大量的请求占用服务器线程进行重试,这时候服务器线程负载就会暴增,致使服务器宕机。对于这种超高并发下的重试设计,咱们不能让重试放在业务线程,而是统一由异步任务来执行。
模板方法设计模式来实现异步重试机制
全部业务类继承重试模板类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; } } }
测试:
启动应用,浏览器输入: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“并发送"重试机制"获取。