关于Feign的超时记录:

Spring Cloud微服务架构中,大部分公司都是利用Open Feign进行服务间的调用,而比较简单的业务使用默认配置是不会有多大问题的,但是如果是业务比较复杂,服务要进行比较繁杂的业务计算,那后台很有可能会出现Read Timeout这个异常。

1、关于hystrix的熔断超时

如果Feign开启了熔断,必须要重新设置熔断超时的时间,因为默认的熔断超时时间太短了,只有1秒,这容易导致业务服务的调用还没完成然后超时就被熔断了。

如何配置熔断超时:

 
  1. #Feign如何开启熔断

  2. feign.hystrix.enabled=true

  3.  
  4. #是否开始超时熔断,如果为false,则熔断机制只在服务不可用时开启(spring-cloud-starter-openfeign中的HystrixCommandProperties默认为true)

  5. hystrix.command.default.execution.timeout.enabled=true

  6.  
  7. #设置超时熔断时间(spring-cloud-starter-openfeign中的HystrixCommandProperties默认为1000毫秒)

  8. hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=6000

注意:关于hystrixapplication.properties配置是没提示的,但是HystrixCommandProperties是会获取的。

 
  1. // 构造函数

  2. protected HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder, String propertyPrefix) {

  3.  
  4. // .... 省略很多其他配置

  5.  
  6. // propertyPrefix:hystrix,key:default

  7. this.executionTimeoutInMilliseconds = getProperty(propertyPrefix, key, "execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds);

  8. }

  9.  
  10. // 具体获取属性的方法

  11. private static HystrixProperty<String> getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, String builderOverrideValue, String defaultValue) {

  12. return HystrixPropertiesChainedProperty.forString().add(propertyPrefix + ".command." + key.name() + "." + instanceProperty, builderOverrideValue).add(propertyPrefix + ".command.default." + instanceProperty, defaultValue).build();

  13. }

2、Feign局部设置超时间

 spring-cloud-dependencies Dalston版本之后,默认Feign对Hystrix的支持默认是关闭的,需要手动开启。

feign.hystrix.enabled=true

开启hystrix,可以选择关闭熔断或超时。
2.1关闭熔断:

 
  1. # 全局关闭熔断:

  2. hystrix.command.default.circuitBreaker.enabled: false

  3. # 局部关闭熔断:

  4. hystrix.command.<HystrixCommandKey>.circuitBreaker.enabled: false

2.2设置超时:

 
  1. # 全局设置超时:

  2. hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 1000

  3. # 局部设置超时:

  4. hystrix.command.<HystrixCommandKey>.execution.isolation.thread.timeoutInMilliseconds: 1000

2.3设置局部方法超时成功案例:

需要对下图两个方法局部设置超时时间为10秒,

 方法一、配置如下:

 
  1. # 禁用Hystrix超时

  2. hystrix.threadpool.default.coreSize = 10

  3. hystrix.command.default.fallback.enabled = true

  4. hystrix.command.default.execution.timeout.enabled= true

  5. hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds = 3000

  6. hystrix.command.MemberStaffFeign#getExcelDataByDoctor(String,Integer,Integer).execution.isolation.thread.timeoutInMilliseconds=10000

  7. hystrix.command.MemberStaffFeign#getExcelDataByTeam(String,Integer,Integer).execution.isolation.thread.timeoutInMilliseconds=10000

  8.  

@SpringBootApplication启动类中添加@EnableCircuitBreaker注解

 
  1. @EnableFeignClients

  2. @EnableEurekaClient

  3. @EnableDiscoveryClient

  4. @EnableApolloConfig

  5. @EnableCircuitBreaker

  6. @SpringBootApplication

  7. public class BusinessSystemApplication {

  8. public static void main(String[] args) {

  9. ConfigurableApplicationContext app = new SpringApplicationBuilder(BusinessSystemApplication.class).run(args);

  10. System.out.println(app.getEnvironment().getProperty("spring.application.name") + "服务启动完毕...");

  11. }

  12. }

2.4关闭超时

 
  1. # 全局关闭:

  2. hystrix.command.default.execution.timeout.enabled: false

  3. # 局部关闭:

  4. hystrix.command.<HystrixCommandKey>.execution.timeout.enabled: false

3、关于Ribbon超时。

Feign调用默认是使用Ribbon进行负载均衡的,所以我们还需要了解关于Ribbon的超时。

①、Feign的调用链路

看一下Feign的请求是否有使用Ribbon的超时时间,而且是如何读取Ribbon的超时时间的?

(1)、org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute

(2)、com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)

(3)、org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory#create

 
  1. ​创建Client,这里会判断对应ClientName的链接Client是否创建过,如果创建过复用之前的Client;

  2. 如果不存在则创建一个并且放入cache缓存。

 
  1. public FeignLoadBalancer create(String clientName) {

  2. FeignLoadBalancer client = this.cache.get(clientName);

  3. if(client != null) {

  4. return client;

  5. }

  6. IClientConfig config = this.factory.getClientConfig(clientName);

  7. ILoadBalancer lb = this.factory.getLoadBalancer(clientName);

  8. ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);

  9. // 判断是否有重试

  10. client = loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,

  11. loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);

  12. this.cache.put(clientName, client);

  13. return client;

  14. }

(4)、com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)

    ​负载均衡器抽象类

(5)、org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute

 
  1. ​ Feign的负载均衡器实现类。到这里我们可以看到,连接超时和读超时的配置都在这里:

  2. 如果application.properties配置文件中的超时时间不为空,则使用配置的超时时间。

  3. 如果为空则使用默认值,而从FeignLoadBalancer的构造函数可以看到,默认值也是取的RibbonProperties的默认超时时间。

 
  1. public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)

  2. throws IOException {

  3. Request.Options options;

  4. // 设置超时时间。,如果orride的配置为空,则用默认值

  5. if (configOverride != null) {

  6. RibbonProperties override = RibbonProperties.from(configOverride);

  7. options = new Request.Options(

  8. override.connectTimeout(this.connectTimeout),

  9. override.readTimeout(this.readTimeout));

  10. }

  11. else {

  12. options = new Request.Options(this.connectTimeout, this.readTimeout);

  13. }

  14. // 发起请求

  15. Response response = request.client().execute(request.toRequest(), options);

  16. return new RibbonResponse(request.getUri(), response);

  17. }

  18.  
  19. // 构造函数

  20. public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, ServerIntrospector serverIntrospector) {

  21. super(lb, clientConfig);

  22. this.setRetryHandler(RetryHandler.DEFAULT);

  23. this.clientConfig = clientConfig;

  24. this.ribbon = RibbonProperties.from(clientConfig);

  25. RibbonProperties ribbon = this.ribbon;

  26. this.connectTimeout = ribbon.getConnectTimeout();

  27. this.readTimeout = ribbon.getReadTimeout();

  28. this.serverIntrospector = serverIntrospector;

  29. }

②、Ribbon的默认超时时间

RibbonClientConfiguration中:

 
  1. public static final int DEFAULT_CONNECT_TIMEOUT = 1000;

  2. public static final int DEFAULT_READ_TIMEOUT = 1000;

③、如何自定义Ribbon超时时间

首先,RibbonProperties的超时时间的读取的源码如下:

 
  1. public Integer getConnectTimeout() {

  2. return (Integer)this.get(CommonClientConfigKey.ConnectTimeout);

  3. }

  4.  
  5. public Integer getReadTimeout() {

  6. return (Integer)this.get(CommonClientConfigKey.ReadTimeout);

  7. }

然后,可以在CommonClientConfigKey中可以看到两个超时时间的名称:

 
  1. // ConnectTimeout:

  2. public static final IClientConfigKey<Integer> ConnectTimeout = new CommonClientConfigKey<Integer>("ConnectTimeout") {};

  3.  
  4. // ReadTimeout:

  5. public static final IClientConfigKey<Integer> ReadTimeout = new CommonClientConfigKey<Integer>("ReadTimeout") {};

然后,在IClientConfig的默认实现类:DefaultClientConfigImpl中,可以发现Ribbon配置的前缀

public static final String DEFAULT_PROPERTY_NAME_SPACE = "ribbon";

所以,最后Ribbon该这么配置超时时间:

 
  1. ribbon.ConnectTimeout=5000

  2. ribbon.ReadTimeout=5000

总结

1.如何配置好HystrixRibbon的超时时间呢?

其实是有套路的,因为Feign的请求:其实是Hystrix+RibbonHystrix在最外层,然后再到Ribbon,最后里面的是http请求。所以说。Hystrix的熔断时间必须大于Ribbon的 ( ConnectTimeout + ReadTimeout )。而如果Ribbon开启了重试机制,还需要乘以对应的重试次数,保证在Ribbon里的请求还没结束时,Hystrix的熔断时间不会超时。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐