Java 远程调用失败重试的操作方法

作者:鸭血粉丝Tang 时间:2021-08-26 21:42:58 

在日常开发的过程中我们经常会需要调用第三方组件或者数据库,有的时候可能会因为网络抖动或者下游服务抖动,导致我们某次查询失败。

这种时候我们往往就会进行重试,当重试几次后依旧还是失败的话才会向上抛出异常进行失败。接下来阿粉就给大家演示一下通常是如何做的,以及如何更优雅的进行重试。

常规做法

我们先来看一下常规做法,常规做法首先会设置一个重试次数,然后通过 while 循环的方式进行遍历,当循环次数没有达到重试次数的时候,直到有正确结果后就返回,如果重试依旧失败则会进行睡眠一段时间,再次重试,直到正常返回或者达到重试次数返回。

package com.example.demo.service;

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

import java.util.Random;
import java.util.concurrent.TimeUnit;

@Service
public class HelloService {
 public String sayHello(String name) {
   String result = "";
   int retryTime = 3;
   while (retryTime > 0) {
     try {
       //
       result = name + doSomething();
       return result;
     } catch (Exception e) {
       System.out.println("send message failed. try again in 1's");
       retryTime--;
       try {
         TimeUnit.SECONDS.sleep(1);
       } catch (InterruptedException ex) {
         throw new RuntimeException(ex);
       }
     }
   }
   return result;
 }

private int doSomething() {
   Random random = new Random();
   int i = random.nextInt(3);
   System.out.println("i is " + i);
   return 10 / i;
 }
}

这里为了模拟异常的情况,阿粉在 doSomething? 函数里面进行了随机数的生成和使用,当随机出来的值为 0 的时候,则会触发 java.lang.ArithmeticException 异常,因为 0 不能作除数。

接下来我们再对外提供一个接口用于访问,代码如下

package com.example.demo.controller;

import com.example.demo.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

@Autowired
 private HelloService helloService;

@GetMapping(value = "/hello")
 public String hello(@RequestParam("name") String name) {
   return helloService.sayHello(name);
 }
}

正常启动过后,我们通过浏览器进行访问。

Java 远程调用失败重试的操作方法

可以看到,我们第一次方法的时候就成功的达到了我们要的效果,随机数就是 0 ,在 1 秒后重试后结果正常。在多试了几次过后,会遇到三次都是 0 的情况,这个时候才会抛出异常,说明服务是真的有问题了。

Java 远程调用失败重试的操作方法

Java 远程调用失败重试的操作方法

Java 远程调用失败重试的操作方法

上面的代码可以看到是有效果了,虽然不是很好看,特别是在还有一些其他逻辑的情况,看上去会很臃肿,但是确实是可以正常使用的,那么有的小伙伴就要问了,有没有一种优雅的方式呢?总不能在很多地方都重复的这样写重试的代码吧。

注解重试

要知道我们普通人在日常开发的时候,如果遇到一个问题肯定是别人都遇到过的,什么时候当我们遇到的问题,没有人遇到过的时候,那说明我们是很前卫的。

因此小伙伴能想到的是不是有简单的方式来进行重试,有的人已经帮我们想好了,可以通过 @Retryable 注解来实现一样的效果,接下来阿粉就给大家演示一下如何使用这个注解。

首先我们需要在启动类上面加入 @EnableRetry? 注解,表示要开启重试的功能,这个很好理解,就像我们要开启定时功能需要添加 @EnableScheduling? 注解一样,Spring? 的 @Enablexxx 注解也是很有意思的,后面我们再聊。

添加完注解以后,需要加入切面的依赖,如下

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

如下不加入这个切面依赖,启动的时候会有如下异常

Java 远程调用失败重试的操作方法

添加的注解和依赖过后,我们需要改造 HelloService? 里面的 sayHello()? 方法,简化成如下,增加  @Retryable 注解,以及设置相应的参数值。

@Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2))
 public String sayHello(String name){
   return name + doSomething();
 }

再次通过浏览器访问 http://127.0.0.1:8080/hello?name=ziyou 我们看到效果如下,跟我们自己写的重试一样。

Java 远程调用失败重试的操作方法

@Retryable 详解

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.retry.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {
   String recover() default "";

String interceptor() default "";

Class<? extends Throwable>[] value() default {};

Class<? extends Throwable>[] include() default {};

Class<? extends Throwable>[] exclude() 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 {};
}

点到这个注解里面,我们可以看到这个注解的代码如下,其中有几个参数我们来解释一下

  • recover:  当前类中的回滚方法名称;

  • interceptor: 重试的 * 名称,重试的时候可以配置一个 * ;

  • value:需要重试的异常类型,跟下面的 include 一致;

  • include:包含的重试的异常类型;

  • exclude:不包含的重试异常类型;

  • label:用于统计的唯一标识;

  • stateful?:标志表示重试是有状态的,也就是说,异常被重新抛出,重试策略是否会以相同的策略应用于具有相同参数的后续调用。如果是false,那么可重试的异常就不会被重新抛出。

  • maxAttempts:重试次数;

  • backoff:指定用于重试此操作的属性;

  • listeners?:重试 * bean 名称;

配合上面的一些属性的使用,我们就可以达到通过注解简单来实现方法调用异常后的自动重试,非常好用。我们可以在执行重试方法的时候设置自定义的重试 * ,如下所示,自定义重试 * 需要实现 MethodInterceptor? 接口并实现 invoke 方法,不过要注意,如果使用了 * 的话,那么方法上的参数就会被覆盖。

package com.example.demo.pid;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.retry.interceptor.RetryInterceptorBuilder;
import org.springframework.retry.interceptor.RetryOperationsInterceptor;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.stereotype.Component;

@Component
public class CustomRetryInterceptor implements MethodInterceptor {

@Override
 public Object invoke(MethodInvocation invocation) throws Throwable {
   RetryOperationsInterceptor build = RetryInterceptorBuilder.stateless()
     .maxAttempts(2).backOffOptions(3000, 2, 1000).build();
   return build.invoke(invocation);
 }
}

自定义回滚方法,我们还可以在重试几次依旧错误的情况,编写自定义的回滚方法。

@Retryable(value = Exception.class,
   recover = "recover", maxAttempts = 2,
   backoff = @Backoff(delay = 1000, multiplier = 2))
 public String sayHello(String name){
   return name + doSomething();
 }

@Recover
 public String recover(Exception e, String name) {
   System.out.println("recover");
   return "recover";
 }

Java 远程调用失败重试的操作方法

要注意:

  • 重试方法必须要使用@Recover 注解;

  • 返回值必须和被重试的函数返回值一致;

  • 参数中除了第一个是触发的异常外,后面的参数需要和被重试函数的参数列表一致;

上面代码中的 @Backoff(delay = 1000, multiplier = 2) 表示第一次延迟 1000ms 重试,后面每次重试的延迟时间都翻倍。

总结

阿粉今天给大家介绍了一下 Spring? 的 @Retryable 注解使用,并通过几个 demo 来带大家编写了自己重试 * 以及回滚方法的时候,是不是感觉用起来会很爽,那还在等什么赶紧用起来吧,其中还有很多细节,只有自己真正的使用过才能体会到。

来源:https://www.51cto.com/article/718769.html

标签:Java,远程调用,重试
0
投稿

猜你喜欢

  • C语言文件操作大全

    2022-10-17 06:25:01
  • springboot实用配置详细图文教程

    2023-12-07 00:36:43
  • Java获取上月份最后一天日期8位的示例代码

    2023-08-12 14:10:38
  • Android HorizontalScrollView滑动与ViewPager切换案例详解

    2023-06-05 00:48:27
  • Java实现数组反转翻转的方法实例

    2021-09-19 00:21:26
  • springboot访问静态资源遇到的坑及解决

    2022-11-06 14:45:11
  • Springboot整合mybatis开启二级缓存的实现示例

    2023-02-24 13:07:18
  • Spring AOP底层原理及代理模式

    2023-05-05 14:19:38
  • 详解Java使用Jsch与sftp服务器实现ssh免密登录

    2023-11-24 18:34:18
  • c#实现sqlserver事务处理示例

    2022-03-28 19:39:50
  • springBoot加入thymeleaf模板的方式

    2023-11-25 14:31:23
  • java如何让带T的时间格式化

    2022-12-22 10:44:28
  • Java基于Tcp/ip连接的多人交互聊天室

    2022-03-31 17:47:35
  • 如何使用Spring AOP的通知类型及创建通知

    2022-03-19 19:32:51
  • Android操作SQLite数据库(增、删、改、查、分页等)及ListView显示数据的方法详解

    2022-11-28 10:03:09
  • java网络爬虫连接超时解决实例代码

    2022-02-02 06:57:28
  • Spring Security实现两周内自动登录"记住我"功能

    2021-12-15 17:59:22
  • 5分钟搭建SpringCloud Eureka服务注册中心的实现

    2022-07-12 05:12:42
  • java网络编程之socket网络编程示例(服务器端/客户端)

    2022-05-02 00:18:59
  • 解决Mybatis映射文件mapper.xml中的注释问题

    2023-09-17 15:06:30
  • asp之家 软件编程 m.aspxhome.com