微服务系列:Spring Cloud 之 Feign、Ribbon、Hystrix 三者超时时间配置

  • Feign 自身有超时时间配置

  • Feign 默认集成的 Ribbon 中也有超时时间配置

  • 假如我们又使用了 Hystrix 来实现熔断降级,Hystrix 自身也有一个超时时间配置

注: spring-cloud-starter-openfeign 低一点的版本中默认集成的有 Hystrix,高版本中又移除了。


一、Feign和 Ribbon

1. 设置 OpenFeign 的超时时间

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

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

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

2. 设置 Ribbon 的超时时间

XML 复制代码
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的异常。

3. 源码追踪

配置都比较简单,接下来我们来追踪一下相关的源码。

首先从 @EnableFeignClients 进去,再到 FeignClientsRegistrar 类中

跟踪到 FeignClientsRegistrar 类中的 registerFeignClient 方法

接着到 FeignClientFactoryBean 类中的 configureUsingProperties 方法

最后一直跟到 feign.Request 中的这里

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

接下来我们来验证一下,修改我们测试用的那个接口,让它睡个 5 秒

XML 复制代码
@GetMapping("/getUserInfo")
public Map<String, Object> getUserInfo(int userId){
   Map<String, Object> map = new HashMap<String, Object>();
   User user = new User(1, "小黑", 26);
   map.put("code", 200);
   map.put("data", user.toString());
   try {
       Thread.sleep(5000);
   } catch (InterruptedException e) {
     e.printStackTrace();
   }
   return map;
}
  • OpenFeign 默认超时时间

此时,我们是要验证 OpenFeign 的默认超时时间,所以在 application.yml 中 feign 和 ribbon 的超时时间都没有设置。

启动项目再次调用我们的老接口:http://localhost:9203/test/getUserInfo?userId=2

疑问? 报错了,连接超时,可是我们代码里睡 5 秒,明明还在超时时间范围内,怎么就连接超时了呐?

其实 OpenFeign 集成了 Ribbon,Ribbon 的默认超时连接时间、读超时时间都是 1 秒,源码在 org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute()方法中,如下图:

断点打到这里(需要访问上面接口才会进断点)会发现:如果OpenFeign 没有设置对应得超时时间,那么将会采用 Ribbon 的默认超时时间

  • 设置 OpenFeign 超时时间
XML 复制代码
feign:
client:
  config:
    default:
      connectTimeout: 8000
      readTimeout: 8000

然后我们重启项目后再访问接口进入上面那个断点看看,发现超时时间变成我们配置的了

接口也返回了正常的结果:

  • 设置 Ribbon 超时时间
XML 复制代码
ribbon:
    ReadTimeout: 7000
    ConectTimeout: 7000

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

原因是 ,OpenFeignRibbon 的超时时间只会有一个生效两者是二选一的,且 OpenFeign 优先。并且,注掉 OpenFeign 超时时间配置之后,就变成了使用设置的 Ribbon 的超时时间,更加验证了OpenFeign 优先。

4. 结论

FeignRibbon 的超时时间只会有一个生效,规则:

  • 如果没有设置过feign超时( 也就是等于默认值的时候)**,**就会读取 ribbon 的配置,使用 ribbon 的超时时间和重试设置。

  • 如果设置了feign超时,则使用 feign 自身的设置。两者是二选一的,且 feign 优先*。*


二、Ribbon 和 Hystrix

1. Hystrix 设置超时时间

XML 复制代码
# 先要开启feign.hystrix.enabled,然后下面这个配置才会起作用
feign:
  hystrix:
    enabled: true

hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 5000

配置好 fallback

XML 复制代码
@FeignClient(contextId = "remoteUserService", value = "cloud-system", fallbackFactory = RemoteUserFallbackFactory.class)

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

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

XML 复制代码
private static final Integer default_executionTimeoutInMilliseconds = 1000;

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

XML 复制代码
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 一般比较短,可以忽略。那么,设置的超时时间应该满足如下,避免ribbon还未重试完就过早的被Hystrix熔断了:

复制代码
(1 + MaxAutoRetries) * (1 + MaxAutoRetriesNextServer)* ReadTimeOut < hystrix 的 *timeoutInMilliseconds

附录1:

一、 Feign设置超时时间

使用Feign调用接口分两层,ribbon的调用和hystrix的调用,所以ribbon的超时时间和Hystrix的超时时间的结合就是Feign的超时时间

XML 复制代码
#hystrix的超时时间
hystrix:
    command:
        default:
            execution:
              timeout:
                enabled: true
              isolation:
                thread:
                  timeoutInMilliseconds: 9000
#ribbon的超时时间
ribbon:
  ReadTimeout: 60000
  ConnectTimeout: 60000

一般情况下 都是 ribbon 的超时时间(<)hystrix的超时时间(因为涉及到ribbon的重试机制)因为ribbon的重试机制和Feign的重试机制有冲突,所以源码中默认关闭Feign的重试机制,源码如下

要开启Feign的重试机制如下:(Feign默认重试五次 源码中有)

复制代码
@Bean
Retryer feignRetryer() {
        return  new Retryer.Default();
}
二、ribbon的重试机制

设置重试次数:

XML 复制代码
ribbon:
  ReadTimeout: 3000
  ConnectTimeout: 3000
  MaxAutoRetries: 1 #同一台实例最大重试次数,不包括首次调用
  MaxAutoRetriesNextServer: 1 #重试负载均衡其他的实例最大重试次数,不包括首次调用
  OkToRetryOnAllOperations: false  #是否所有操作都重试

根据上面的参数计算重试的次数:MaxAutoRetries+MaxAutoRetriesNextServer+(MaxAutoRetries *MaxAutoRetriesNextServer) 即重试3次 则一共产生4次调用如果在重试期间,时间超过了hystrix的超时时间,便会立即执行熔断,fallback。所以要根据上面配置的参数计算hystrix的超时时间,使得在重试期间不能达到hystrix的超时时间,不然重试机制就会没有意义。hystrix超时时间的计算:(1 + MaxAutoRetries + MaxAutoRetriesNextServer) * ReadTimeout 即按照以上的配置 hystrix的超时时间应该配置为 (1+1+1)*3=9秒

当ribbon超时后且hystrix没有超时,便会采取重试机制。当OkToRetryOnAllOperations设置为false时,只会对get请求进行重试。如果设置为true,便会对所有的请求进行重试,如果是put或post等写操作,如果服务器接口没做幂等性,会产生不好的结果,所以OkToRetryOnAllOperations慎用。

如果不配置ribbon的重试次数,默认会重试一次注意: 默认情况下,GET方式请求无论是连接异常还是读取异常,都会进行重试 非GET方式请求,只有连接异常时,才会进行重试

附录2 spring cloud ribbon配置自动重试

spring cloud 通过eureka 访问其他服务默认没有重试机制,需要额外进行配置实现客户端重试,特别是对方服务在进行滚动发布的时候,本地维护的节点信息还没有更新,就需要进行自动重试,保证前端业务流畅。

pom文件添加依赖:

复制代码
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

应用添加配置:

复制代码
ribbon.ReadTimeout=30000
ribbon.ConnectTimeout=5000
ribbon.SocketTimeout=30000
ribbon.MaxAutoRetries=0
ribbon.MaxAutoRetriesNextServer=2
ribbon.OkToRetryOnAllOperations=true
ribbon.ServerListRefreshInterval=5000

spring.cloud.loadbalancer.retry.enable=true

客户端使用可以直接根据服务名进行访问了:服务内注入restTemplate进行外部服务访问:

复制代码
BaseResponse response = restTemplate.postForObject("http://xxx-service/cellphone", req, new BaseResponse<String>().getClass());

4 补充实现AbstractLoadBalancingClient的类有

4.1 RetryableOkHttpLoadBalancingClient(spring-retry)开启条件

XML 复制代码
# 导入spring-retry 依赖
ribbon.okhttp.enabled=true
ribbon.httpclient.enabled=false

4.2 OkHttpLoadBalancingClient开启条件

XML 复制代码
ribbon.okhttp.enabled=true
ribbon.httpclient.enabled=false

4.3 RibbonLoadBalancingHttpClient开启条件默认

4.4 RetryableRibbonLoadBalancingHttpClient(spring-try)开启条件

XML 复制代码
spring.cloud.loadbalancer.retry.enabled=true
zuul.retryable=true
#导入spring-try
#Retry 次数计算
#reTry次数的计算= (MaxAutoRetries*+1)*(MaxAutoRetriesNextServer+1)

超时最大时间

ribbonTimeout=(ribbonReadTimeout+ ribbonConnectTimeout) *(maxAutoRetries + 1) * (maxAutoRetriesNextServer+ 1)

gateway:

复制代码
- name: Retry
args:
retries: 1
methods: GET,POST
#多个参数用-连接
statuses: BAD_GATEWAY
#参考同上,series与statuses二选一即可
series:
  #表示5xx,以5开头的各种状态码

- SERVER_ERROR
  exceptions:
      #有以下异常时触发重试,此处注意timeout的时间与熔断设置的时间

   - java.util.concurrent.TimeoutException
     - java.net.ConnectException

get io异常 重试;post io异常 不重试;

ribbon resttemplate:

复制代码
spring.cloud.loadbalancer.retry.enabled=true
ribbon.ConnectTimeout=1000
ribbon.ReadTimeout=10000
ribbon.OkToRetryOnAllOperations=false
ribbon.MaxAutoRetriesNextServer=2
ribbon.maxAutoRetries=0

get 在发生io异常的时候回进行重试;post 在发生io异常不会进行重试;开启Hystrix

同样的,Feign中已经内置了Hystrix,直接通过配置来开启Hystrix,如下所示:

复制代码
feign:
	hsytrix:
		enabled: true
开启Feign功能

在服务消费方的引导类上添加注解@EnableFeignClients,用于开启Feign功能。

优化Feign第一次调用的策略

Feign初始化开销

懒加载(Lazy Loading):Feign客户端通常是在需要时才进行初始化的,这种机制被称为懒加载。当第一次调用Feign客户端时,它会执行一系列的初始化操作,包括加载配置、创建代理对象、解析服务地址、建立连接池等。这些操作都需要一定的时间来完成,因此第一次调用自然会相对较慢。

服务发现和注册:如果你的应用使用了服务注册与发现机制(如Eureka、Consul等),Feign在第一次调用时还需要从注册中心获取服务的实例信息。这个过程涉及到网络通信和DNS解析,可能会因为网络延迟或注册中心的性能问题而变慢。

线程池和连接池初始化:Feign在进行远程调用时,通常会使用线程池来管理线程,以及连接池来管理HTTP连接。第一次调用时,这些资源可能还没有初始化好,Feign需要创建新的线程和连接,这也会增加调用的启动时间。

类加载和代理生成:Feign使用动态代理来生成客户端代码,第一次调用时可能需要加载和生成相关的类,这同样会增加调用的启动时间。

Ribbon饥饿加载:开启Ribbon的饥饿加载模式,让Ribbon在应用启动时就完成服务列表的加载和缓存,避免在第一次调用时进行这些操作。配置方式如下:

XML 复制代码
ribbon:
  eager-load:
    enabled: true  
    clients: your-feign-client-name # 注解 @FeignClient 中的 value 值写在此处
Feign配置压缩

Spring Cloud Feign能够对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。直接通过配置feign中的参数即可开启压缩功能,如下所示:

复制代码
feign:
	compression:
		request:
			enabled: true #请求压缩
			mime-types: text/html,application/xml,application.json #压缩的数据类型
			mim-request-size: 2048 #设置触发压缩的大小下限
		response:
			enabled: true #响应压缩

Feign支持的日志级别

(1) NONE: 不记录任何日志信息,默认

(2) BASIC: 只记录请求的方法,URL以及响应状态码和执行时间

(3) HEADERS:在BASIC的基础上,添加了请求和响应的头信息

(4) FULL:记录所有请求和响应的明细,包括头信息,请求体,元数据

(1)开启Feign日志配置文件写法:

开启Feign的日志功能,可以帮助你更清晰地了解Feign在调用过程中的行为,包括初始化过程、请求发送、响应接收等。这有助于你定位问题并进行优化。配置方式如下:

复制代码
feign:
  client:  
    config:  
      default:  
        loggerLevel: full
  
logging:  
  level:  
    com.yourpackage.feignclient: debug
(2)开启Feign日志的配置类写法:

添加Feign的配置类,定义日志级别。注意,要在该配置类上添加@Configuration表示这是一个配置类。

复制代码
package com.springboot.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConfig {

    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

Feign Hystrix 熔断、线程使用坑点:

hystrx 官方配置解释地址:

https://github.com/Ne[tf](https://m.hqchip.com/app/1522 "tf")lix/Hystrix/wiki/Configuration

线程池队列配置问题

常用配置:

复制代码
​
#核心线程池大小,默认值为:10 
hystrix.threadpool.default.coreSize=10 

#调用超时时间,默认值为1000ms 
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=15000 

#最大线程池大小,这是在不开始拒绝的情况下可以支持的最大并发量。默认值为10。 
hystrix.threadpool.default.maximumSize=50 

#队列大小拒绝阈值,默认值为5。即使maxQueueSize未达到也会发生拒绝。在maxQueueSize==-1时不生效。 
hystrix.threadpool.default.queueSizeRejectionThreshold=100 

#maximumSize配置是否生效,默认值为false。maximumSize可以等于或高于coreSize。 
#设置coreSize< maximumSize 创建一个可以维持maximumSize并发性的线程池; 
# 但会在相对不活动期间将线程返回给系统。(受限于keepAliveTimeInMinutes)。 
hystrix.threadpool.default.allowMaximumSizeToDivergeFromCoreSize = true 

# 最大队列大小,默认值为 -1。
# 值为-1时:使用 thenSynchronousQueue;
# 值为正值时:使用 LinkedBlockingQueue。 
hystrix.threadpool.default.maxQueueSize = 50

错误配置①:

复制代码
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=1000
复制代码
feign使用的是懒加载,第一次调用时,会初始化各种bean,速度很慢,默认1秒很容易超时。

错误配置②:

复制代码
hystrix.threadpool.default.coreSize=10
hystrix.threadpool.default.maxQueueSize=1000
hystrix.threadpool.default.queueSizeRejectionThreshold=20

因为 queueSizeRejectionThreshold 太小,实际上在并发达到 30 以上的时候,就会拒绝后面的请求了。

错误配置③:

复制代码
hystrix.threadpool.default.coreSize=10
hystrix.threadpool.default.maxQueueSize=20
hystrix.threadpool.default.queueSizeRejectionThreshold=1000

因为 maxQueueSize 太小,实际上在并发达到 30 以上的时候,就会拒绝后面的请求了。

相关推荐
fanly112 天前
Surging AI Agent 完整产品介绍
微服务·microservice
吃饱了得干活4 天前
Spring Cloud Gateway 微服务网关:路由、断言、过滤器
java·spring cloud
蝎子莱莱爱打怪9 天前
XZLL-IM干货系列 04|Netty 长连接实战:Pipeline 怎么排、心跳怎么跳、连接怎么管
后端·微服务·面试
SamDeepThinking10 天前
Java微服务练习方式
java·后端·微服务
米丘13 天前
微前端之 Web Components 完全指南
微服务·html
霸道流氓气质15 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
慧一居士16 天前
Feign的GET请求如何传递对象参数?
java·spring cloud
我登哥MVP16 天前
SpringCloud Alibaba 核心组件解析:服务链路追踪
java·spring boot·后端·spring·spring cloud·java-ee·maven
慧一居士16 天前
SpringCloud 微服务Feigin 用的完整调用端和被调用的示例
java·spring cloud
霸道流氓气质16 天前
Spring Boot 微服务性能优化完全指南
spring boot·微服务·性能优化