Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >spring cloud feign调用超时重试retryer

spring cloud feign调用超时重试retryer

作者头像
xdd
发布于 2022-07-12 06:20:34
发布于 2022-07-12 06:20:34
1.7K00
代码可运行
举报
文章被收录于专栏:java技术鸡汤java技术鸡汤
运行总次数:0
代码可运行
  • 认识一下Retryer接口
  • 认识一下RetryableException异常
  • 认识一下FeignException异常
  • 实际中我们是如何来应用的
Retry 接口 简单介绍
  • 通过下面的源码,Retry接口继承了Cloneable接口。
  • Retry接口里面有一个方法叫continueOrPropagate,参数是一个RetryableException重试异常的对象,返回值为void类型
  • Retry接口还有 一个clone()方法,返回类型是Retryer
  • 该接口里面有个静态内部类Default,并且实现了Retryer接口
    • 该类有一个默认构造函数,还有一个有参数的构造函数

源码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package feign;

import static java.util.concurrent.TimeUnit.SECONDS;

对于克隆每次调用`Client.execute(Request, Request.Options)` 实现可以保持状态,以确定是否重试操作应该继续。
public interface Retryer extends Cloneable {

  /**
   * 如果重试被允许,返回(睡觉后可能)。 否则传播例外。
   */
  void continueOrPropagate(RetryableException e);

  Retryer clone();

  public static class Default implements Retryer {

    // 最大重试次数
    private final int maxAttempts;
    // 重试的间隔
    private final long period;
    // 最大重试间隔
    private final long maxPeriod;
    int attempt;
    long sleptForMillis;

    // Default类的默认无参构造函数,
    // 重试间隔100 ms,最大重试间隔1s,最大重试次数默认5次
    public Default() {
      this(100, SECONDS.toMillis(1), 5);
    }

    // 重试间隔,最大重试间隔,最大重试次数,attempt默认是1
    public Default(long period, long maxPeriod, int maxAttempts) {
      this.period = period;
      this.maxPeriod = maxPeriod;
      this.maxAttempts = maxAttempts;
      this.attempt = 1;
    }

    // visible for testing;
    protected long currentTimeMillis() {
      return System.currentTimeMillis();
    }

    // 重写了Retryer的方法continueOrPropagate
    public void continueOrPropagate(RetryableException e) {
      // 如果重试的次数attempt大于最大重试次数,则抛出重试异常对象RetryableException
      if (attempt++ >= maxAttempts) {
        throw e;
      }

      long interval;
      if (e.retryAfter() != null) {
        interval = e.retryAfter().getTime() - currentTimeMillis();
        if (interval > maxPeriod) {
          interval = maxPeriod;
        }
        if (interval < 0) {
          return;
        }
      } else {
        interval = nextMaxInterval();
      }
      try {
        Thread.sleep(interval);
      } catch (InterruptedException ignored) {
        Thread.currentThread().interrupt();
      }
      sleptForMillis += interval;
    }

    /**
     * 计算时间间隔为重试尝试。 的间隔呈指数增加每次尝试,在nextInterval * = 1.5(其中,1.5是回退因子)的速率,在最大间隔。
     * @return 时间从现在纳秒,直到下一次尝试。
     */
    long nextMaxInterval() {
      long interval = (long) (period * Math.pow(1.5, attempt - 1));
      return interval > maxPeriod ? maxPeriod : interval;
    }

    @Override
    public Retryer clone() {
      return new Default(period, maxPeriod, maxAttempts);
    }
  }

  /**
   * 实现永不重试请求。 它传播RetryableException
   */
  Retryer NEVER_RETRY = new Retryer() {

    @Override
    public void continueOrPropagate(RetryableException e) {
      throw e;
    }

    @Override
    public Retryer clone() {
      return this;
    }
  };
}
RetryableException简单介绍
  • 该异常继承FeignException,也是一个RuntimeException
  • 里面有一个定义的Long类型的变量retryAfter
  • 该类有两个构造函数,分别是:
    • RetryableException(String message, Throwable cause, Date retryAfter)
    • RetryableException(String message, Date retryAfter)
  • 该类还有一个无参数的方法,叫做retryAfter,会返回一个Date类型

源码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package feign;

import java.util.Date;

/**
 * 当引发此异常Response被认为是可重试,通常经由feign.codec.ErrorDecoder当status是503
 */
public class RetryableException extends FeignException {

  private static final long serialVersionUID = 1L;

  private final Long retryAfter;

  /**
   * retryAfter -通常对应于Util.RETRY_AFTER报头。
   */
  public RetryableException(String message, Throwable cause, Date retryAfter) {
    super(message, cause);
    this.retryAfter = retryAfter != null ? retryAfter.getTime() : null;
  }

  /**
   * retryAfter -通常对应于Util.RETRY_AFTER报头。
   */
  public RetryableException(String message, Date retryAfter) {
    super(message);
    this.retryAfter = retryAfter != null ? retryAfter.getTime() : null;
  }

  /**
   * http->503 服务不可用
   * 有时对应于Util.RETRY_AFTER存在于报头503的状态。其他的时间就从专用响应解析。空如果不明
   */
  public Date retryAfter() {
    return retryAfter != null ? new Date(retryAfter) : null;
  }
}
FeignException 简单介绍
  • 该类继承了RuntimeException
  • 有一个int类型的私有变量status,用来表示HTTP的状态码
  • 有三个方法,分别是:
    • errorReading(Request request, Response ignored, IOException cause)
    • errorStatus(String methodKey, Response response)
    • errorExecuting(Request request, IOException cause)
  • 主要异常是I/O类的可以进行重试,404无重试效果

源码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package feign;

import java.io.IOException;

import static java.lang.String.format;

public class FeignException extends RuntimeException {

  private static final long serialVersionUID = 0;
  // HTTP status
  private int status;

  protected FeignException(String message, Throwable cause) {
    super(message, cause);
  }

  protected FeignException(String message) {
    super(message);
  }

  protected FeignException(int status, String message) {
    super(message);
    this.status = status;
  }

  public int status() {
    return this.status;
  }

  static FeignException errorReading(Request request, Response ignored, IOException cause) {
    return new FeignException(
        format("%s reading %s %s", cause.getMessage(), request.method(), request.url()),
        cause);
  }

  public static FeignException errorStatus(String methodKey, Response response) {
    String message = format("status %s reading %s", response.status(), methodKey);
    try {
      if (response.body() != null) {
        String body = Util.toString(response.body().asReader());
        message += "; content:\n" + body;
      }
    } catch (IOException ignored) { // NOPMD
    }
    return new FeignException(response.status(), message);
  }

  static FeignException errorExecuting(Request request, IOException cause) {
    return new RetryableException(
        format("%s executing %s %s", cause.getMessage(), request.method(), request.url()), cause,
        null);
  }
}
如何在项目中应用重试机制?

在上面的介绍中,可以知道Retryer接口,Default类,重试异常类RetryerException,我们可以通过重写Retryer接口的方法continueOrPropagate来实现重试,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Slf4j
public class ConnectTimeoutRetryer extends Retryer.Default {
    Supplier<Stream<String>> streamSupplier = () -> Stream.of("connect timed out");

    public ConnectTimeoutRetryer(){
        super();
    }

    @Override
    public void continueOrPropagate(RetryableException e) {
        // 在kibana上可以分析prd上由于feign超时,都会在cause里面有connect time out关键字,因此这里做判断,如果异常原因里面都不是connect time out的,会打印ConnectTimeoutRetryerFeign failed,并抛出RetryableException对象e
        if (streamSupplier.get().noneMatch(x -> e.getCause().getMessage().contains(x))) {
            log.warn("ConnectTimeoutRetryerFeign failed", e);
            throw e;
        }
        log.error("begin to retry:{} ,{}" , e.getMessage(), e);
        super.continueOrPropagate(e);
    }

    //重写retryer的clone方法
    @Override
    public Retryer clone() {
        return new ConnectTimeoutRetryer();
    }
}

我们这个方案,主要是解决,各个微服务的feign调用之间超时问题,比如网络不稳定等原因导致的。

下面是重试时的堆栈信息:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
2020-05-28 21:17:08,954 [hystrix-zis-zzzz-193] ERROR [com.xxxx.common.service.share.feign.ConnectTimeoutRetryer] [?:?] [trace=xxx,span=xxx] - begin to retry:connect timed out executing POST http://xxx.com/search/rrr ,{} feign.RetryableException: connect timed out executing POST http://xxx.com/search/rrr at feign.FeignException.errorExecuting(FeignException.java:67) at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:104) at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:108) at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302) at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298) at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46) at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.Observable.unsafeSubscribe(Observable.java:10211) at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:51) at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35) at rx.Observable.unsafeSubscribe(Observable.java:10211) at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41) at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.Observable.unsafeSubscribe(Observable.java:10211) at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94) at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:56) at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:47) at org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixConcurrencyStrategy$HystrixTraceCallable.call(SleuthHystrixConcurrencyStrategy.java:188) at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction.call(HystrixContexSchedulerAction.java:69) at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Caused by: java.net.SocketTimeoutException: connect timed out at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:589) at sun.net.NetworkClient.doConnect(NetworkClient.java:175) at sun.net.www.http.HttpClient.openServer(HttpClient.java:463) at sun.net.www.http.HttpClient.openServer(HttpClient.java:558) at sun.net.www.http.HttpClient.<init>(HttpClient.java:242) at sun.net.www.http.HttpClient.New(HttpClient.java:339) at sun.net.www.http.HttpClient.New(HttpClient.java:357) at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1220) at sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1156) at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1050) at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:984) at sun.net.www.protocol.http.HttpURLConnection.getOutputStream0(HttpURLConnection.java:1334) at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1309) at feign.Client$Default.convertAndSend(Client.java:133) at feign.Client$Default.execute(Client.java:73) at org.springframework.cloud.sleuth.instrument.web.client.feign.TraceFeignClient.execute(TraceFeignClient.java:92) at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:97) ... 32 common frames omitted

缺点:该方案是可以解决各个微服务之间feign调用超时的问题,但是Supplier<Stream<String>> streamSupplier = () -> Stream.of("connect timed out");灵活度不够,只有堆栈cause中有connect time out的时候才会抛出重试异常RetryerException去进行重试。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java技术鸡汤 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
聊聊feign的Retryer
feign-core-10.2.3-sources.jar!/feign/Retryer.java
code4it
2019/07/17
1.1K0
聊聊feign的Retryer
SpringCloud升级之路2020.0.x版-26.OpenFeign的组件
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 首先,我们给出官方文档中的组件结构图: [外链图片转存失败,源站可能有防盗链机制,
干货满满张哈希
2021/12/29
8810
SpringCloud升级之路2020.0.x版-26.OpenFeign的组件
一次由于OOM导致锁没有释放的定位流程(结合Arthas)
最近发现业务上返回慢,并且feign的fallback被触发了。查看日志,发现触发了重试,是什么触发的重试呢,通过异常堆栈发现:
干货满满张哈希
2021/04/12
1.5K0
聊聊openfeign的超时和重试
openfeign是一种声明式的http客户端,它可以方便地集成到springcloud,像调用本地方法一样使用http方式调用远程服务。今天我们来聊一聊feign的超时和重试。
jinjunzhu
2020/11/19
5.9K0
面试系列之-Spring Cloud Feign
根据传入的Bean对象和注解信息,从中提取出相应的值,来构造Http Request 对象;
用户4283147
2023/11/20
3600
面试系列之-Spring Cloud Feign
【SpringBoot系列】微服务接口调用框架Feign学习指南
通常一个服务需要调用 Http 端点,Feign 来自 OpenFeign 项目使得以声明式方式调用 http 端点变得更加容易。Spring 通过其 Spring Cloud OpenFeign 集成了 openfeign 集成。
Freedom123
2024/04/25
8660
Spring Cloud openFeign学习【3.0.2版本】
内容分为openFeign大致的使用和源码的个人解读,里面参考了不少其他优秀博客作者的内容,很多地方基本算是拾人牙慧了,不过还是顺着源码读了一遍加深理解。
阿东
2021/08/16
1.7K0
Spring Cloud openFeign学习【3.0.2版本】
Spring Cloud-Feign设计原理
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://louluan.blog.csdn.net/article/details/82821294
亦山
2019/05/25
2.7K0
Spring Cloud OpenFeign 超时与重试
1、连接超时 (connectTimeout) 和 读取超时 (readTimeout) 同时配置时,才会生效。
冯文议
2022/01/11
2.6K0
spring cloud feign调用原理_vip解析的原理
Feign 是⼀个 HTTP 请求的轻量级客户端框架。通过 接口 + 注解的方式发起 HTTP 请求调用,面向接口编程,而不是像 Java 中通过封装 HTTP 请求报文的方式直接调用。服务消费方拿到服务提供方的接⼝,然后像调⽤本地接⼝⽅法⼀样去调⽤,实际发出的是远程的请求。让我们更加便捷和优雅的去调⽤基于 HTTP 的 API,被⼴泛应⽤在 Spring Cloud 的解决⽅案中。
全栈程序员站长
2022/11/10
5K0
spring cloud feign调用原理_vip解析的原理
SpringCloud重试机制配置
SpringCloud重试retry是一个很赞的功能,能够有效的处理单点故障的问题。主要功能是当请求一个服务的某个实例时,譬如你的User服务启动了2个,它们都在eureka里注册了,那么正常情况下当请求User服务时,ribbon默认会轮询这两个实例。此时如果其中一个实例故障了,发生了宕机或者超时等,如果没有配置启用重试retry策略,那么调用方就会得到错误信息或者超时无响应或者是熔断返回的信息。我们希望的自然是一个故障了,会自动切换到另一个去访问。
天涯泪小武
2019/01/17
1.3K0
Feign源码解析4:调用过程
前面几篇分析了Feign的初始化过程,历经艰难,可算是把@FeignClient注解的接口对应的代理对象给创建出来了。今天看下在实际Feign调用过程中的一些源码细节。
低级知识传播者
2024/01/17
4260
Feign源码解析4:调用过程
Feign原理 (图解)_feign原理
从上图可以看到,Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的 Request 请求。通过Feign以及JAVA的动态代理机制,使得Java 开发人员,可以不用通过HTTP框架去封装HTTP请求报文的方式,完成远程服务的HTTP调用。
全栈程序员站长
2022/11/10
4.3K0
Feign原理 (图解)_feign原理
[享学Feign] 六、原生Feign的解码器Decoder、ErrorDecoder
代码下载地址:https://github.com/f641385712/feign-learning
YourBatman
2020/02/21
19.2K0
[享学Feign] 六、原生Feign的解码器Decoder、ErrorDecoder
【翻译】怎么自定义feign的重试机制
在微服务框架中,通过rest api的方式调用其他服务是很正常的事情。在spring生态系统中,一个流行的REST客户端是Feign,这是因为它的声名式风格和添加不同配置的DRY方式。
伍六七AI编程
2022/03/23
1.1K0
Feign其实也不是很难
Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求。Spring Cloud引入 Feign并且集成了Ribbon实现客户端负载均衡调用。
用户3467126
2021/08/05
5860
深入理解Feign之源码解析
什么是Feign Feign是受到Retrofit,JAXRS-2.0和WebSocket的影响,它是一个jav的到http客户端绑定的开源项目。 Feign的主要目标是将Java Http 客户端变
方志朋
2017/12/29
5K0
深入理解Spring cloud源码篇之Feign源码
在上一篇文章中分析了Eureka的注册、续约、服务剔除、服务自我保护等机制,地址在https://blog.csdn.net/lgq2626/article/details/80288992 。这篇分析SpringCloud的feign。SpringCloud微服务项目之间调用是通过httprest请求来进行服务调用的,之前我们会用到HttpClient等工具来进行服务请求,Spring对这种请求进行了处理,封装成了可声明式的web客户端,使得编写web客户端更容易,feign还支持可插拔的编码器和解码器,Spring在用的时候增加了对@requestMapping的处理,同时,SpringCloud还对feign集成了注册中心(eureka)和客户端负载均衡(ribbon),使得我们拥有一个客户端负载均衡的web请求客户端。
chengcheng222e
2021/11/04
4570
[享学Feign] 三、原生Feign的核心API详解(一):UriTemplate、HardCodedTarget...
代码下载地址:https://github.com/f641385712/feign-learning
YourBatman
2020/02/21
4.3K0
[享学Feign] 三、原生Feign的核心API详解(一):UriTemplate、HardCodedTarget...
Spring Openfeign与Ribbon,Hystrix的调用流程分析
Spring Openfeign作为一个声明式的REST Client,可以为应用中,尤其是微服务之间的调用上节省很多工作量,同时跟同为Netflix体系的Ribbon和Hystrix整合使用,可以为系统提供客户端负载均衡以及熔断保障。
TimGallin
2021/11/02
1.2K0
相关推荐
聊聊feign的Retryer
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验