Feign、Ribbon、Hystrix(铁三角)以及三者超时时间配置
作者:mmseoamin日期:2023-12-14

在微服务架构中很多功能都需要调用多个服务才能完成某一项功能,一个成熟的微服务集群,内部调用必然依赖一个好的 RPC 框架,比如:基于 Http 协议的 Feign,基于私有 tcp 协议的 Dubbo 

1. Feign 是什么

Feign 是Spring Cloud Netflix组件中的轻量级Restful的 HTTP 服务客户端,实现了负载均衡和 Rest 调用的开源框架,封装了Ribbon和RestTemplate, 实现了WebService的面向接口编程,进一步降低了项目的耦合度。

Feign 通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,封装了 Http 调用流程。

 

Feign、Ribbon、Hystrix(铁三角)以及三者超时时间配置,第1张

1、Robbin

(1)客户端的软负载

Robbin是springcloud的LB调用组件,提供客户端的软件负载均衡。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随即连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。

Robbin 提供的客户端软负载,是SpringCloud微服务的典型特征之一。

Feign、Ribbon、Hystrix(铁三角)以及三者超时时间配置,第2张

2)负载均衡策略

  • 轮询(RoundRobin),以轮询的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。
  • 随机(Random),随机选择状态为UP的Server。
  • 加权响应时间(WeightedResponseTime),根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。
  • 区域感知轮询(ZoneAvoidanceRule),复合判断server所在区域的性能和server的可用性选择server。

    (3)核心组件

    Ribbon的核心组件(均为接口类型)有以下几个,

    • ServerList,用于获取地址列表。它既可以是静态的(提供一组固定的地址),也可以是动态的(从注册中心中定期查询地址列表)。
    • ServerListFilter,仅当使用动态ServerList时使用,用于在原始的服务列表中使用一定策略过虑掉一部分地址。
    • IRule,选择一个最终的服务地址作为LB结果。选择策略有轮询、根据响应时间加权、断路器(当Hystrix可用时)等。

      Ribbon在工作时首选会通过ServerList来获取所有可用的服务列表,然后通过ServerListFilter过虑掉一部分地址,最后在剩下的地址中通过IRule选择出一台服务器作为最终结果。

       

      2、Fegin

      Fegin是一个声明式Http端调用,集成了Robbin的负载均衡功能,同时声明式调用更加方便(只需要简单的注解即可)。简单的可以理解为:Spring Cloud Feign 的出现使得Eureka和Ribbon的使用更加简单。

      (1)Fegin接口示例

      a、启动类@EnableFeignClients 注解

      @SpringBootApplication
      @EnableEurekaClient
      @EnableFeignClients // 启用fegin声明式调用
      public class FeginComsumerApplication {
          public static void main(String[] args) {
              SpringApplication.run(FeginComsumerApplication.class, args);
          }
      }
      

      b、声明一个调用的Feign接口,

      @Service
      @FeignClient(name = "name-service")
      public interface NameService {
          @RequestMapping(value = "/getName", method = RequestMethod.GET)
          public String getName();
      }
      

      c、服务端提供接口实现

      @RequestMapping(value = "/getName", method = RequestMethod.GET)
      public String getName(){
          return "hello world";
      }

      d、fegin声明是调用

      @Autowired
      private NameServiceClient feginNameServiceClient;
      @RequestMapping(value = "/getName", method= RequestMethod.GET)
      public String getName(){
          return feginNameServiceClient.getName();
      }
      

      (2)Fegin 的类加载流程

      • 通过主类上的EnableFeignClients 注解开启FeignClient;
      • 根据Feign 的规则实现接口,并加上FeignClient注解,供调用的地方注入调用;
      • 程序启动后,会扫描所有FeignClient 注解的类,并将这些信息注入到IOC 容器中;
      • 当b中接口被调用时,通过jdk代理,以及反射(Spring处理注解的方式),来生成具体的RequestTemplate
      • RequestTemplate 生成Reqest
      • Request 交给httpclient处理,这里的httpclient 可以是OkHttp,也可以是HttpUrlConnection 或者HttpClient
      • 最后Client被封装到LoadBalanceClient类,这个类结合Ribbon 实现负载均衡。

         

        (3)Fegin的原理

        Feign、Ribbon、Hystrix(铁三角)以及三者超时时间配置,第3张

        3、Hystrix

        Hystrix 是springcloud生态的断路器(隔离、限流、降级),主要是用来预防服务雪崩的现象,剔除掉分布式系统中某些挂掉或请求过慢的服务节点。Hystrix是一个帮助解决分布式系统中超时处理和容错的类库, 拥有保护系统的能力。

        (1)隔离、限流、降级

        Hystrix断路器有两种隔离策略:信号量隔离(默认)和线程池隔离。

        信号量模式从始至终都只有请求线程自身,是同步调用模式,不支持超时调用,不支持直接熔断,由于没有线程的切换,开销非常小。

        线程池模式可以支持异步调用,支持超时调用,支持直接熔断,存在线程切换,开销大。

        Feign、Ribbon、Hystrix(铁三角)以及三者超时时间配置,第4张

        信号量隔离:常用于获取共享资源的场景中,比如计算机连接了两个打印机,那么初始的信号量就是2,被某个进程或线程获取后减1,信号量为0后,需要获取的线程或进程进入资源等待状态。Hystrix的处理有些不同,其不等待,直接返回失败。

        线程池隔离:采用的就是jdk的线程池,其默认选用不使用阻塞队列的线程池,例如线程池大小为10,如果某时刻10个线程均被使用,那么新的请求将不会进入等待队列,而是直接返回失败,起到限流的作用。

        此外,其还引入了一个断路器机制,当断路器处于打开状态时,直接返回失败或进入降级流程。断路器打开和关闭的触发流程为:当总的请求数达到可阈值HystrixCommandProperties.circuitBreakerRequestVolumeThreshold(),或总的请求失败百分比达到了阈值HystrixCommandProperties.circuitBreakerErrorThresholdPercentage(),这时将断路器的状态由关闭设置为打开。当断路器打开时,所有的请求均被短路,在经过指定休眠时间窗口后,让下一个请求通过(断路器被认为是半开状态)。如果请求失败,断路器进入打开状态,并进入新的休眠窗口;否则进入关闭状态。
        Feign、Ribbon、Hystrix(铁三角)以及三者超时时间配置,第5张

         流程如上图所示,Hystrix框架通过命令模式来实现方法粒度上的服务保障,主要涉及HystrixCommand和HystrixObservableCommand类,前者提供同步的execute和异步的queue方法,后者提供立即执行observe和延迟执行toObservable的回调方法。此外,实际项目中通常不会使用Hystrix集成的本地缓存。

         

        配置问题

        Feign 如何设置超时时间(connectionTimeout、readTimout

        对于这个问题网上有很多相关资料,大体上有两种方案,一种是通过设置 ribbon 的超时时间(因为 Feign 是基于 ribbon 来实现的,所以通过 ribbon 的超时时间设置也能达到目的),一种是直接设置 Feign 的超时时间,我将会在下边的篇幅里分别说一下如何通过application.yml 配置文件来设置超时时间

        1、Ribbon

        对于 ribbon 又分为全局配置和指定服务配置:

        • 全局配置

          对所有的服务该配置都生效

          ribbon:  
              ReadTimeout: 30000 #单位毫秒
              ConnectTimeout: 30000 #单位毫秒
        • 指定服务配置

          下边代码中的 jettopro-basic 是服务的名称,意思是该配置只针对名为 annoroad-beta 的服务有效,根据实际的需要替换成你自己的服务名

          jettopro-basic:
            ribbon:
              ReadTimeout: 30000 #单位毫秒
              ConnectTimeout: 30000 #单位毫秒

          2、Feign

          与 Ribbon 一样,Feign 也分为全局配置和指定服务配置:

        • 全局配置

          下边代码中使用的 feign.client.config.default ,意思是所有服务都采用该配置

          feign:
            client:
              config:
                default:
                  connectTimeout: 10000 #单位毫秒
                  readTimeout: 10000 #单位毫秒
          • 指定服务配置

            下边代码中使用的 feign.client.config.jettopro-basic,意思是该配置只针对名为 jettopro-basic 的服务有效,可以根据实际的需要替换成你自己的服务名

            feign:
              client:
                config:
                  jettopro-basic:
                    connectTimeout: 10000 #单位毫秒
                    readTimeout: 10000 #单位毫秒

            3、总结

            如果同时配置了Ribbon、Feign,那么 Feign 的配置将生效

            Ribbon 的配置要想生效必须满足微服务相互调用的时候通过注册中心,如果你是在本地通过 @FeignClient 注解的 url 参数进行服务相互调用的测试,此时 ribbon 设置的超时时间将会失效,但是通过 Feign 设置的超时时间不会受到影响(仍然会生效)

            综上所述建议使用 Feign 的来设置超时时间

             

            基本介绍

            1. Feign 是什么

            Feign 是Spring Cloud Netflix组件中的轻量级Restful的 HTTP 服务客户端,实现了负载均衡和 Rest 调用的开源框架,封装了Ribbon和RestTemplate, 实现了WebService的面向接口编程,进一步降低了项目的耦合度。

            Feign 通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,封装了 Http 调用流程。

            2. 为什么要使用 Feign

            如果不使用 RPC 框架,那么调用服务需要走 Http 的话,配置请求 head、body,然后才能发起请求。获得响应体后,还需解析等操作,十分繁琐。

            Feign 旨在使编写 JAVA HTTP 客户端变得更加简单,Feign 简化了RestTemplate代码,实现了Ribbon负载均衡,使代码变得更加简洁,也少了客户端调用的代码,使用 Feign 实现负载均衡是首选方案,只需要你创建一个接口,然后在上面添加注解即可。

            Feign 是声明式服务调用组件,其核心就是:像调用本地方法一样调用远程方法,无感知远程 HTTP 请求。让开发者调用远程接口就跟调用本地方法一样的体验,开发者完全无感知这是远程方法,无需关注与远程的交互细节,更无需关注分布式环境开发。

            3. OpenFeign

            Feign 内置了Ribbon,用来做客户端负载均衡调用服务注册中心的服务。

            Feign 支持的注解和用法参考官方文档:https://github.com/OpenFeign/feign官方文档,使用 Feign 的注解定义接口,然后调用这个接口,就可以调用服务注册中心的服务。

            Feign本身并不支持Spring MVC的注解,它有一套自己的注解,为了更方便的使用Spring Cloud孵化了OpenFeign。并且支持了Spring MVC的注解,如@RequestMapping,@PathVariable等等。

            OpenFeign的@FeignClient可以解析Spring MVC的@RequestMapping注解下的接口,并通过动态代理方式产生实现类,实现类中做负载均衡调用服务。

            • Feign 采用的是基于接口的注解
            • Feign 整合了 Ribbon,具有负载均衡的能力
            • 整合了 Hystrix,具有熔断的能力

              一、Feign 和 Ribbon

              1. 设置 OpenFeign 的超时时间

              我们首先来看一下 OpenFeign 自己的请求超时配置,直接在 yml 文件中配置:

              eign:
                # 设置 feign 超时时间
                client:
                  config:
                    # default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
                    default:
                      connectTimeout: 5000
                      readTimeout: 5000
              

              default 默认是全局的,将 default 换成某个服务的名称可以设置单个服务的超时时间

              2. 设置 Ribbon 的超时时间

              ribbon:
                # 建立链接所用的时间,适用于网络状况正常的情况下, 两端链接所用的时间
                ReadTimeout: 5000
                # 指的是建立链接后从服务器读取可用资源所用的时间
                ConectTimeout: 5000

              注意这两个参数设置的时候没有智能提示

              ConnectTimeout:

              指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间。

              在java中,网络状况正常的情况下,例如使用 HttpClient 或者 HttpURLConnetion 连接时设置参数 connectTimeout=5000 即5秒,如果连接用时超过5秒就是抛出 java.net.SocketException: connetct time out 的异常。

               

              ReadTimeout:

              指的是建立连接后从服务器读取到可用资源所用的时间。

              在这里我们可以这样理解ReadTimeout:正常情况下,当我们发出请求时可以收到请求的结果,也就是页面上展示的内容,但是当网络状况很差的时候,就会出现页面上无法展示出内容的情况。另外当我们使用爬虫或者其他全自动的程序时,无法判断当前的网络状况是否良好,此时就有了ReadTimeout的用武之地了,通过设置ReadTimeout参数,例:ReadTimeout=5000,超过5秒没有读取到内容时,就认为此次读取不到内容并抛出Java.net.SocketException: read time out的异常。

               

              源码分析

              可以发现 OpenFeign 的默认的 connectTimeout 是 10 秒,readTimeout 是 60 秒

              此时,我们是要验证 OpenFeign 的默认超时时间,所以在 application.yml 中 feign 和 ribbon 的超时时间都没有设置。报错了,连接超时,可是我们代码里睡 5 秒,明明还在超时时间范围内,怎么就连接超时了呐?其实 OpenFeign 集成了 Ribbon,Ribbon 的默认超时连接时间、读超时时间都是 1 秒

              如果 OpenFeign 没有设置对应得超时时间,那么将会采用 Ribbon 的默认超时时间

              • 设置 OpenFeign 超时时间
                feign:
                  client:
                    config:
                      default:
                        connectTimeout: 8000
                        readTimeout: 8000
              • 设置 Ribbon 超时时间
              • ribbon:
                  ReadTimeout: 7000
                  ConectTimeout: 7000

                重复上面步骤,断点进去一看 ??? 怎么还是 8000

                原因是 OpenFeign 和 Ribbon 的超时时间只会有一个生效两者是二选一的,且 OpenFeign 优先

                注掉 OpenFeign 超时时间配置之后,就变成了使用设置的 Ribbon 的超时时间

                4. 结论
                Feign 和 Ribbon 的超时时间只会有一个生效,规则:如果没有设置过 feign 超时,也就是等于默认值的时候,就会读取 ribbon 的配置,使用 ribbon 的超时时间和重试设置。否则使用 feign 自身的设置。两者是二选一的,且 feign 优先。

                二、Ribbon 和 Hystrix

                1. Hystrix 设置超时时间

                # 设置 hystrix 超时时间
                feign:
                  hystrix:
                    enabled: true
                hystrix:
                  command:
                    default:
                      execution:
                        isolation:
                          thread:
                            timeoutInMilliseconds: 6000
                

                @FeignClient(contextId = "remoteUserService", value = "cloud-system", fallbackFactory = RemoteUserFallbackFactory.class)

                 注意:如果没有配置 fallback,那么 hystrix 的超时就不会生效,而是由 ribbon 来控制。

                hystrix 的默认超时时间是 1s,这个配置在 HystrixCommandProperties 类

                设置 hystrix 超时时间比 ribbon 大(OpenFign 的超时时间注掉)

                ribbon:
                  ReadTimeout: 2000
                  ConectTimeout: 2000

                访问地址 http://localhost:9203/test/getUserInfo?userId=2 发现请求 2s 左右就返回了,这个值刚好是 ribbon.ReadTimeout 的时间。表示此时 ribbon 超时触发了。然后进入了 hystrix 的熔断过程。

                2. 结论:

                • 如果请求时间超过 ribbon 的超时配置,会触发重试;
                • 在配置 fallback 的情况下,如果请求的时间(包括 ribbon 的重试时间),超出了 ribbon 的超时限制,或者 hystrix 的超时限制,那么就会熔断。

                  一般来说,会设置 ribbon 的超时时间 < hystrix, 这是因为 ribbon 有重试机制。(这里说的 ribbon 超时时间是包括重试在内的,即,最好要让 ribbon 的重试全部执行,直到 ribbon 超时被触发)。

                   

                  由于 connectionTime 一般比较短,可以忽略。那么,设置的超时时间应该满足:

                  (1 + MaxAutoRetries) * (1 + MaxAutoRetriesNextServer)* ReadTimeOut < hystrix 的 *timeoutInMillisecond

                  微服务系列:服务调用 Spring Cloud 之 OpenFeign 详细入门_程序猿秃头之路的博客-CSDN博客

                  微服务系列:服务调用 Spring Cloud 之 OpenFeign 性能优化_程序猿秃头之路的博客-CSDN博客

                  微服务系列:Spring Cloud 之 Feign、Ribbon、Hystrix 三者超时时间配置_feign和ribbon超时时间设置_程序猿秃头之路的博客-CSDN博客

                  错误1:springcloud异常:timed-out and no fallback available,failed and no fallback available的问题解决 

                  timed-out and no fallback available:

                  这个错误基本是出现在Hystrix熔断器,熔断器的作用是判断该服务能不能通,如果通了就不管了,调用在指定时间内超时时,就会通过熔断器进行错误返回。

                  一般设置如下配置的其中一个即可:

                  1、把时间设长

                  这里设置5秒

                  hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000  

                  2、把超时发生异常属性关闭

                  hystrix.command.default.execution.timeout.enabled=false  

                  3、禁用feign的hystrix 

                  feign.hystrix.enabled: false  

                  failed and no fallback available:

                  而通过上面设置只是针对熔断器的错误关闭,并不能解决根本问题,比如Feign客户端调用远程服务时,默认为8秒超时时间,如果在规定时间内没有返回,同样会跳转到熔断器进行处理。即使关闭了熔断器的错误,但是总的错误处理还会是有这个问题出现。

                  那么要解决根本问题,就要从请求超时时间入手,因为有些服务可能存在调用时间长的问题,所以直接配置:

                  ribbon.ReadTimeout=60000
                  ribbon.ConnectTimeout=60000

                  这些才是真正解决请求超时的问题,如果不设置这个,被调用接口很慢时,会出现Read Timeout on Request。

                  而针对调用失败重试的次数也可以设置:

                  ribbon.maxAutoRetries=0

                  参考:

                  1. https://github.com/spring-cloud/spring-cloud-netflix/issues/321

                  2. https://stackoverflow.com/questions/38080283/how-to-solve-timeout-feignclient

                  3. http://www.itmuch.com/spring-cloud-feign-ribbon-first-request-fail/

                  4. https://github.com/spring-cloud/spring-cloud-netflix/issues/696

                  5. http://www.jianshu.com/p/0eb13fd033a8

                  6. http://blog.csdn.net/qwlzxx/article/details/77163268

                  7. http://blog.csdn.net/clementad/article/details/54315805