SpringCloud 之 Ribbon/Feign/Hystrix 的超时、重试问题总结

本是古典 何须时尚 2022-12-20 01:54 372阅读 0赞

Hi,我是空夜,又是一周不见!

今天来讲讲 ribbon 和 feign 中超时时间应该如何配置。

Spring Cloud 中,一般会用 feign 或者 ribbon 来进行服务调用,ribbon 还自带了负载均衡、重试机制。而feign 是基于 ribbon 的。

通常,为了保证服务的高可用,防止雪崩等问题的出现,还会引入 hystrix。hystrix 的熔断也跟超时时间有关系。

如何统筹考虑 ribbon、feign、hystrix 三者之间的关系,添加合适的配置,使得各个组件各司其职、协调合作, 是一个麻烦的问题。

想想就令人头秃。

aa6293200362241176a632de3d04de19.png

今天我就来理清一下 ribbon、feign、hystrix 之间的超时关系。

首先有一个推论:

feign 集成了 ribbon 和 hystrix,feign 本身不带超时限制,其超时是由 ribbon 和 hystrix 控制的。

因此,我们仅需要理清 ribbon 和 hystrix 之间的超时关系即可。

5e580cae53a7d71b16b803085e8d693c.png


下面以 ribbon 为例,分别测试在默认情况下、与 hystrix 整合使用情况下的超时情况。

1. ribbon 的默认配置

ribbon 的默认配置在 DefaultClientConfigImpl 这个类中。

  1. public static final int DEFAULT_READ_TIMEOUT = 5000;
  2. public static final int DEFAULT_CONNECTION_MANAGER_TIMEOUT = 2000;
  3. public static final int DEFAULT_CONNECT_TIMEOUT = 2000;

注意,这里出现了第一个天坑:虽然 DefaultClientConfigImpl 这个类里指定了 DEFAULT_READ_TIMEOUT 为 5000 ms,但是,debug 发现,这个默认值在构建 ribbon 的 clientConfig 时,被替换掉了。

具体如下:

在使用 ribbon 请求接口时,第一次会构建一个 IClienConfig 对象,这个方法在 RibbonClientConfiguration 类中,此时,重新设置了 ConnectTimeout、ReadTimeout、GZipPayload

  1. public class RibbonClientConfiguration {
  2. /** * Ribbon client default connect timeout. */
  3. public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
  4. /** * Ribbon client default read timeout. */
  5. public static final int DEFAULT_READ_TIMEOUT = 1000;
  6. /** * Ribbon client default Gzip Payload flag. */
  7. public static final boolean DEFAULT_GZIP_PAYLOAD = true;
  8. @RibbonClientName
  9. private String name = "client";
  10. @Autowired
  11. private PropertiesFactory propertiesFactory;
  12. @Bean
  13. @ConditionalOnMissingBean
  14. public IClientConfig ribbonClientConfig() {
  15. DefaultClientConfigImpl config = new DefaultClientConfigImpl();
  16. config.loadProperties(this.name);
  17. config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
  18. config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
  19. config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
  20. return config;
  21. }
  22. //...
  23. }

综上,ribbon 的默认 ConnectTimeout 和 ReadTimeout 都是 1000 ms

7b756f90403cd896d012edd34be3da7c.png

下面我们看看自定义配置。


2. 自定义 ribbon 的配置

咱们通过 ribbon.xxx 来自定义配置,看看能不能生效:

  1. ribbon:
  2. OkToRetryOnAllOperations: true #对所有操作请求都进行重试,默认false
  3. ReadTimeout: 1000 #负载均衡超时时间,默认值5000
  4. ConnectTimeout: 3000 #请求连接的超时时间,默认值2000
  5. MaxAutoRetries: 1 #对当前实例的重试次数,默认0
  6. MaxAutoRetriesNextServer: 0 #重试切换实例的次数,默认1

测试发现不起作用。怎么肥事?网上有些码友的文章里就是这样写的啊。

56881109f30d9947d7697bf16649accf.png

原因很简单:必须要添加 ribbon.http.client.enabled = true 的配置,自定义 ribbon 的超时配置才能生效。

  1. ribbon:
  2. http:
  3. client:
  4. enabled: true

下面咱们来测试一下超时和重试机制:

(贴心的我已经给了测试截图了,希望你们爱我,害!)

我这里启用了一个 producer 服务来提供接口,该接口大概是这样的:

  1. @GetMapping(value = "hello/{name}")
  2. public String hello(@PathVariable("name") String name, Integer mills) {
  3. logger.info("开始执行请求,name: " + name + "要求暂停:" + mills + "毫秒");
  4. if (mills != null && mills > 0) {
  5. try {
  6. Thread.sleep(mills);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. }
  11. return "hello, [" + name + "], this is service producer by nacos.....";
  12. }

注意有一个 mills 参数用于指定 producer 接口的等待时间,这样可以测试出 consumer 服务(也就是利用 ribbon 来调用 producer 接口的服务)的超时、重试机制。

consumer 大概是这样的:

  1. @GetMapping(value = "test")
  2. //@HystrixCommand(fallbackMethod = "testHystrix")
  3. public String test(String name, Integer mills) {
  4. logger.info("开始请求 producer,其暂停时间为:" + mills);
  5. String producerRes = restTemplate.getForObject(
  6. "http://" + service_producer_name + "/producer/hello/" + name + "?mills=" + mills, String.class);
  7. logger.info("请求获取成功,开始打印请求结果:");
  8. String res = "测试consumer/test接口,基于ribbon调取服务server-producer的hello接口,返回:" + producerRes;
  9. System.out.println(res);
  10. return res;
  11. }

测试代码准备好了,咱们开始测试:

3a4c0419b85258a77c7ae46de7dc68a1.png

首先,当前实例重试一次:

c5b46d04f1dfd019d4401c4cc9784129.png

请求的 ReadTimeOut 设置为 5s,1 + MaxAutoRetries = 2,两次,刚好 10s

我们看下 producer 端:
173677114ba7c3e9e7dcec3ac7af5e00.png

下面将 MaxAutoRetries 和 MaxAutoRetriesNextServer 都设为 1:
703626ab49206db013a816ae8ddc19b6.png

看下 producer 端,每个 5s 接收到一个请求,共 4个:

7a57458d9f02387d3ef41b1202690fa8.png

为什么会请求四次呢?

为什么将 MaxAutoRetriesNextServer 设为 1,会使得请求增加了 2 次重试呢?

MaxAutoRetriesNextServer 直接翻译过来是:下一个服务的最大重试次数。

这个土味翻译听起来就像是对下一个服务重试的次数。那应该是1吗?这里的下一个服务又指的是什么呢?

3f3a19a52e966d9f278fdbeb2e18326b.png

不要慌,小场面。咱们改一下配置,MaxAutoRetries 设置为 2,MaxAutoRetriesNextServer 设为 3;

这里我们启动两个同名的 producer,那么这个服务就有两个实例了;

测试一下:
e2156812c90b407506ed3f5e2721be01.png

(贴心的我已经给在图里给你们标出来分析的结果了,是不是很暖男?)

另一个 producer:

b9ae232af131880c7268e055615439ce.png
根据日志里的时间推算,MaxAutoRetriesNextServer 真正的意思应该是:如果请求失败,最大要切换多少次服务实例(不管具体有多少个实例,即使一个实例,也会切换回这个实例本身 MaxAutoRetriesNextServer 次 )

下面我们得出结论:ribbon 中,请求最多会被执行—— 1 + maxAutoRetries + (maxAutoRetries + 1) * MaxAutoRetriesNextServer

也就是 (1 + maxAutoRetries ) * (1 + MaxAutoRetriesNextServer) 次

f93e263e0820d168c11768ca7c1d8de1.gif

ribbon 这个渣男的套路已经被我们解析透彻了。下面我们将面对 ribbon + hystrix 的渣男+暖男组合。


3. ribbon 集成 hystrix 后的超时、重试配置

为什么要叫 hystrix 暖男呢?当然是有原因的。

hystrix 是一个服务降级、限流、熔断组件。可以有效保证微服务平台的稳定性,避免雪崩等现象的发生。所以说,hystrix 还是很暖的。

727005bef1989cabf80ece1ef8066b65.png

ribbon 集成 hystrix 很简单:

启动类添加 @EnableHystrix 注解。

接口上添加 @HystrixCommand ,配置 fallback 方法:

  1. @GetMapping(value = "test")
  2. @HystrixCommand(fallbackMethod = "testHystrix")
  3. public String test(String name, Integer mills) {
  4. logger.info("开始请求 producer,其暂停时间为:" + mills);
  5. String producerRes = restTemplate.getForObject(
  6. "http://" + service_producer_name + "/producer/hello/" + name + "?mills=" + mills, String.class);
  7. logger.info("请求获取成功,开始打印请求结果:");
  8. String res = "测试consumer/test接口,基于ribbon调取服务server-producer的hello接口,返回:" + producerRes;
  9. System.out.println(res);
  10. return res;
  11. }
  12. /** * test接口的断路器 * @param name * @return */
  13. private String testHystrix(String name, Integer mills) {
  14. return "sorry, " + name + ", this service is unavailable temporarily. We are returning the defaultValue by hystrix.";
  15. }

测试看看:

producer 暂停 500ms, 正常

producer 暂停 1000ms,请求被 @HystrixCommand 指定的 fallback 方法处理:

bd24ae8ca5f8f90f9e39dba05ed8cf44.png

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

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

  1. private static final Integer default_executionTimeoutInMilliseconds = 1000; // default => executionTimeoutInMilliseconds: 1000 = 1 second
  2. protected HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder, String propertyPrefix) {
  3. // ...
  4. this.executionTimeoutEnabled = getProperty(propertyPrefix, key, "execution.timeout.enabled", builder.getExecutionTimeoutEnabled(), default_executionTimeoutEnabled);
  5. // ...
  6. }

继续测试 ribbon 和 hystrix 超时时间的关系。

配置好 hystrix 的 fallback 后,修改配置文件,设置 hystrix 的超时时间,使其大于 ribbon 的超时时间:

  1. ribbon:
  2. OkToRetryOnAllOperations: true #对所有操作请求都进行重试,默认false
  3. ReadTimeout: 2000 #负载均衡超时时间,默认值5000
  4. ConnectTimeout: 3000 #ribbon请求连接的超时时间,默认值2000
  5. MaxAutoRetries: 0 #对当前实例的重试次数,默认0
  6. MaxAutoRetriesNextServer: 0 #对切换实例的重试次数,默认1
  7. # 如果不添加 ribbon.http.client.enabled=true,那么 ribbon 的默认配置不会生效
  8. http:
  9. client:
  10. enabled: true
  11. hystrix:
  12. command:
  13. default: #default全局有效,service id指定应用有效
  14. execution:
  15. timeout:
  16. #如果enabled设置为false,则请求超时交给ribbon控制,为true,则超时作为熔断根据
  17. enabled: true
  18. isolation:
  19. thread:
  20. timeoutInMilliseconds: 10000 #断路器超时时间,默认1000ms

此时,用 postman 发送消息:

a6298a393df72f2d6f25c2c17161a7d8.png
我们可以看到,请求 2s 左右就返回了,这个值刚好是 ribbon.ReadTimeout 的时间。表示此时 ribbon 超时触发了。然后进入了 hystrix 的熔断过程。


4. 结论

总结一下:

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

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

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

(1 + MaxAutoRetries) \ (1 + MaxAutoRetriesNextServer) ReadTimeOut < hystrix 的 timeoutInMilliseconds\


今日的分享到此结束,记得点个关注点个!我的公众号是:猿生物语(ID:JavaApes

5f297a2e82adc8adaaa8c78bc4b909d8.gif

发表评论

表情:
评论列表 (有 0 条评论,372人围观)

还没有评论,来说两句吧...

相关阅读

    相关 超时机制

    网上发现这篇好文章,这里记录学习。 介绍       在实际开发过程中,笔者见过太多故障是因为超时没有设置或者设置的不对而造成的。而这些故障都是因为没有意识到超时设置的