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

目录
  • 常规做法
  • 注解重试
  • @Retryable 详解
  • 总结

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

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

常规做法

我们先来看一下常规做法,常规做法首先会设置一个重试次数,然后通过 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);
  }
}

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

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

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

注解重试

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

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

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

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

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

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

添加的注解和依赖过后,我们需要改造 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 我们看到效果如下,跟我们自己写的重试一样。

@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";
  }

要注意:

  • 重试方法必须要使用@Recover 注解;
  • 返回值必须和被重试的函数返回值一致;
  • 参数中除了第一个是触发的异常外,后面的参数需要和被重试函数的参数列表一致;

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

总结

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

到此这篇关于Java 远程调用失败重试的操作方法的文章就介绍到这了,更多相关Java 远程调用失败内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java编程Retry重试机制实例详解

    本文研究的主要是Java编程Retry重试机制实例详解,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下 1.业务场景 应用中需要实现一个功能: 需要将数据上传到远程存储服务,同时在返回处理成功情况下做其他操作.这个功能不复杂,分为两个步骤:第一步调用远程的Rest服务逻辑包装给处理方法返回处理结果:第二步拿到第一步结果或者捕捉异常,如果出现错误或异常实现重试上传逻辑,否则继续逻辑操作. 2.常规解决方案演化 1)try-catch-redo简单重试模式: 包装正

  • Java基于Guava Retrying实现重试功能

    在接口调用中由于各种原因,可能会重置失败的任务,使用Guava-Retrying可以方便的实现重试功能. 首先,需要引用Guava-Retrying的包 <dependency> <groupId>com.github.rholder</groupId> <artifactId>guava-retrying</artifactId> <version>2.0.0</version> </dependency>

  • java 重试框架 sisyphus 入门介绍

    What is Sisyphus sisyphus综合了 spring-retry 和 gauva-retrying 的优势,使用起来也非常灵活. 为什么选择这个名字 我觉得重试做的事情和西西弗斯很相似. 一遍遍的重复,可能徒劳无功,但是乐此不疲. 人一定要想象西西弗斯的快乐.--加缪 其他原因 以前看了 java retry 的相关框架, 虽然觉得其中有很多不足之处.但是没有任何重复造轮子的冲动,觉得是徒劳无功的. 当然这段时间也看了 Netty 的接口设计,和 Hibernate-Valid

  • Java远程调用Shell脚本并获取输出信息【推荐】

    1.添加依赖 <dependency> <groupId>ch.ethz.ganymed</groupId> <artifactId>ganymed-ssh2</artifactId> <version>262</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId&g

  • Java 重试框架 Sisyphus 配置的两种方式

    目录 1.函数式配置概览 1.1 默认配置 2.方法说明 2.1 condition 2.2 retryWaitContext 2.3 maxAttempt 2.4 listen 2.5 recover 2.6 callable 2.7 retryCall 3.接口的详细介绍 3.1 接口及其实现 3.2 用户自定义 3.3 sisyphus 注解 4.设计的规范 4.1 maven 引入 4.2 Retry 4.3 RetryWait 5.注解的使用 5.1 Proxy+CGLIB 5.2 S

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

    目录 常规做法 注解重试 @Retryable 详解 总结 在日常开发的过程中我们经常会需要调用第三方组件或者数据库,有的时候可能会因为网络抖动或者下游服务抖动,导致我们某次查询失败. 这种时候我们往往就会进行重试,当重试几次后依旧还是失败的话才会向上抛出异常进行失败.接下来阿粉就给大家演示一下通常是如何做的,以及如何更优雅的进行重试. 常规做法 我们先来看一下常规做法,常规做法首先会设置一个重试次数,然后通过 while 循环的方式进行遍历,当循环次数没有达到重试次数的时候,直到有正确结果后就

  • spring retry方法调用失败重试机制示例解析

    目录 前言 1.导入依赖 2.注解的使用 3.开启重试 补充,手动声明式重试: 前言 很多场景会用到重试的机制,比如:rpc服务调用失败重试,文件上传oss失败重试,http接口调用失败重试,支付回调失败重试等等,一切因为网络,非逻辑性错误等不确定因素引起的失败都可以加上重试的机制,来增强系统的健壮性,博主也处理过文件上传到第三方oss服务失败增加重试的事例,在这之前不知道spring有个spring-retry项目,所以采用的是限制次数的递归调用的方式来解决的. 现在我们来看看spring b

  • Java远程调用组件Feign技术使用详解

    目录 一. 概要 二. Feign简介 1. 概念 2. 功能 三. 服务提供者 1. 添加依赖 2. 配置文件 3. 启动类 4. 控制层 5. POJO 四. 服务消费者 1. 添加依赖 2. 配置文件 3. 启动类 4. Feign服务 5. 控制层 五. 测试 1. 测试get请求 2. 测试post请求json数据格式 3. 测试头部中包含信息 一. 概要 我们知道,现在最火且最有技术含量的技术莫过于SpringCloud微服务了,所以今天壹哥就带大家来学习一下微服务的核心的组件之一,

  • 使用Spring Cloud Feign远程调用的方法示例

    在Spring Cloud Netflix栈中,各个微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端.我们可以使用JDK原生的URLConnection.Apache的Http Client.Netty的异步HTTP Client, Spring的RestTemplate.但是,用起来最方便.最优雅的还是要属Feign了. Feign简介 Feign是一个声明式的Web服务客户端,使用Feign可使得Web服务客户端的写入更加方便. 它具有可插拔注释支持

  • java远程连接调用Rabbitmq的实例代码

    本文介绍了java远程连接调用Rabbitmq,分享给大家,希望此文章对各位有所帮助. 打开IDEA创建一个maven工程(Java就可以了). pom.xml文件如下 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apac

  • SpringCloud中的Feign远程调用接口传参失败问题

    目录 Feign远程调用接口传参失败 这是调用者 这是feign的client 这是被调者 Feign远程调用的注意点 定义的做远程调用的api接口 service微服务中的Controller的参数绑定 Feign远程调用接口传参失败 我在做一个微服务调用的时候出现了被调接口传参失败问题 Feign是通过http协议调用服务的,后来发现是因为Gep和Maping不一致,还有使用feign时要记得给实体类加无参构造注解 同时这些注解都尽量一致,不然微服务调bug很麻烦. 这是调用者 这是feig

  • Spring Cloud负载均衡及远程调用实现详解

    负载均衡 使用微服务后,为了能够承担高并发的压力,同一个服务可能会启动多个实例.这时候消费者就需要负载均衡,把请求分散到各个实例.负载均衡主要有两种设计: 服务端负载均衡客户端负载均衡 对于传统的分布式服务来说,大多使用服务端负载均衡.一般会使用Nginx或者ELB等工具作为负载均衡器,如下图: 传统负载均衡 而在Spring Cloud中,使用的是「客户端负载均衡」的方式,使用「Ribbon」组件来实现客户端的负载均衡.只要引入了微服务注册中心依赖,就会自动引入Ribbon依赖.客户端负载均衡

  • 从零搭建脚手架之集成Spring Retry实现失败重试和熔断器模式(实战教程)

    目录 背景 实战 添加依赖 启用重试 @Recover @CircuitBreaker 高级实战 方式一 @CircuitBreaker + RetryTemplate 方式二 @CircuitBreaker + @Retryable 参考 背景 在我们的大多数项目中,会有一些场景需要重试操作,而不是立即失败,让系统更加健壮且不易发生故障. 场景如下: 瞬时网络抖动故障 服务器重启 偶发死锁 某些上游的异常或者响应码,需要进行重试 远程调用 从数据库中获取或存储数据 以上皆为瞬时故障. 也会有一

  • SpringBoot Http远程调用的方法

    本文实例为大家分享了SpringBoot Http远程调用的具体代码,供大家参考,具体内容如下 一.在实现远程调用时可以使用feign与http远程调用,两者的关系有一下几点: feign.http,有时候在调用第三方api的时候.使用httpclient,别人的接口不可能提供它的配置,自己项目框架是spring的,使用feign相互配置,都是okhttpclient的方式.Feign是一个接口声明式调用框架,实现了一个抽象层的逻辑,没有真正实现底层http请求,提供了一个client接口用于实

随机推荐