Spring Cloud组件那么多超时设置,我们如何理解和运用?

前言

Spring Cloud 作为微服务解决方案 全家桶,集合了丰富的微服务组件,如GatewayFeignHystrix,RibbonOkHttpEureka等等。而作为服务调用环节涉及到的几个组件:FeignHystrix,RibbonOkHttp 都有超时时间的设置,Spring Cloud 是如何优雅地把它们协调好呢?

Spring Cloud 在接口调用上,大致会经过如下几个组件配合,如下图:

1)当一个接口请求的时候:

当调用被@FeignClient注解修饰的接口时,在框架内部,会将请求转换成Feign的请求实例feign.Request,然后交由Feign框架处理。

2)Feign :转化请求

Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。
Feign被广泛应用在Spring Cloud 的解决方案中,是学习基于Spring Cloud 微服务架构不可或缺的重要组件。
开源项目地址:https://github.com/OpenFeign/feign

3)Hystrix :熔断处理机制

Feign的调用关系,会被Hystrix代理拦截,对每一个Feign调用请求,Hystrix都会将其包装成HystrixCommand,参与Hystrix的流控和熔断规则。如果请求判断需要熔断,则Hystrix直接熔断,抛出异常或者使用FallbackFactory返回熔断Fallback结果;如果通过,则将调用请求传递给Ribbon组件。

4)Ribbon :服务地址选择

当请求传递到Ribbon之后,Ribbon会根据自身维护的服务列表,根据服务的服务质量,如平均响应时间,Load等,结合特定的规则,从列表中挑选合适的服务实例,选择好机器之后,然后将机器实例的信息请求传递给Http Client客户端,HttpClient客户端来执行真正的Http接口调用;

5)HttpClient :Http客户端,真正执行Http调用

根据上层Ribbon传递过来的请求,已经指定了服务地址,则HttpClient开始执行真正的Http请求。

 

每个组件自己有独立的接口调用超时设置参数,详细介绍如下:

feign的默认配置:

1.配置在FeignClientProperties类中

2.feign 的配置可以采用feign.client.config.<feginName>....的格式为每个feign客户端配置

3.对于默认值,可以使用feign.client.config.default..的方式进行配置

下面给出配置,可以按照需求选择

feign:
  client:
    config:
      <feignName>:
        connectTimeout: 5000 ## 网络连接时间
        readTimeout: 5000 ## 读超时时间
        loggerLevel: full
        errorDecoder: com.example.SimpleErrorDecoder
        retryer: com.example.SimpleRetryer
        requestInterceptors:
          - com.example.FooRequestInterceptor
          - com.example.BarRequestInterceptor
        decode404: false
        encoder: com.example.SimpleEncoder
        decoder: com.example.SimpleDecoder
        contract: com.example.SimpleContract

Spring Cloud 加载feign配置项的原理:

检查是否Feign是否制定了上述的配置项,即是否有FeignClientProperties实例; 如果有上述的配置项,则表明Feign是通过properties初始化的,即configureUsingProperties; 根据配置项feign.client.defaultToProperties的结果,使用不同的配置覆盖策略。

feign初始化的过程,其实就是构造Feign.Builder的过程,如下图所示:

相关代码实现如下:

protected void configureFeign(FeignContext context, Feign.Builder builder) {
        FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
        if (properties != null) {
            if (properties.isDefaultToProperties()) {
                configureUsingConfiguration(context, builder);
                configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
                configureUsingProperties(properties.getConfig().get(this.name), builder);
            } else {
                configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
                configureUsingProperties(properties.getConfig().get(this.name), builder);
                configureUsingConfiguration(context, builder);
            }
        } else {
            configureUsingConfiguration(context, builder);
        }
    }

场景分析

场景1:没有通过配置文件配置,在这种模式下,将使用configureUsingConfiguration,此时将会使用Spring 运行时自动注入的Bean完成配置

protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {
        Logger.Level level = getOptional(context, Logger.Level.class);
        if (level != null) {
            builder.logLevel(level);
        }
        Retryer retryer = getOptional(context, Retryer.class);
        if (retryer != null) {
            builder.retryer(retryer);
        }
        ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
        if (errorDecoder != null) {
            builder.errorDecoder(errorDecoder);
        }
        Request.Options options = getOptional(context, Request.Options.class);
        if (options != null) {
            builder.options(options);
        }
        Map<String, RequestInterceptor> requestInterceptors = context.getInstances(
                this.name, RequestInterceptor.class);
        if (requestInterceptors != null) {
            builder.requestInterceptors(requestInterceptors.values());
        }

        if (decode404) {
            builder.decode404();
        }
    }

默认情况下,Spring Cloud对此超时时间的设置为:

connectTimeoutMillis = 10 * 1000
readTimeoutMillis = 60 * 1000

场景2:配置了FeignClientProperties,并且配置了feign.client.defaultToProperties = true,此时的这种场景,其配置覆盖顺序如下所示: configureUsingConfiguration---> configurationUsingPropeties("default")----> configurationUsingProperties("<client-name>") 如下图配置所示,最终超时时间为:connectionTimeout=4000,readTimeout=4000

feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000
      <client-name>:
        connectTimeout: 4000
        readTimeout: 4000

场景3:配置了FeignClientProperties,并且配置了feign.client.defaultToProperties = false,此时的这种场景,配置覆盖顺序是: configurationUsingPropeties("default")----> configurationUsingProperties("<client-name>")---> configureUsingConfiguration 如果按照这种策略,则最终的超时时间设置就为connectionTimeout=10000,readTimeout=6000

Feign的超时时间的意义:
feign 作为最前端暴露给用户使用的,一般其超时设置相当于对用户的一个承诺,所以Spring在处理这一块的时候,会有意识地使用feign的超时时间来设置后面的ribbon 和http client组件。
需要注意的是:hystrix的超时处理和feign之间在当前的Spring Cloud框架规划中,并没有相关关系

Hystrix的超时设置

Hystrix的超时设置,在于命令执行的时间,一般而言,这个时间要稍微比Feign的超时时间稍微长些,因为Command除了请求调用之外,还有一些业务代码消耗。hystrix的配置规则和feign的风格比较类似:hystrix.command.<service-name>

hystrix.command.default.execution.isolation.strategy = THREAD
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds = 10000
hystrix.command.default.execution.timeout.enabled = true
hystrix.command.default.execution.isolation.thread.interruptOnTimeout = true
hystrix.command.default.execution.isolation.thread.interruptOnFutureCancel = false

Hystrix超时时间存在的意义
Hystrix的超时时间是站在命令执行时间来看的,和Feign设置的超时时间在设置上并没有关联关系。Hystrix不仅仅可以封装Http调用,还可以封装任意的代码执行片段。Hystrix是从命令对象的角度去定义,某个命令执行的超时时间,超过此此时间,命令将会直接熔断。
假设hystrix 的默认超时时间设置了10000,即10秒,而feign 设置的是20秒,那么Hystrix会在10秒到来是直接熔断返回,不会等到feign的20秒执行结束,也不会中断尚未执行完的feign调用。

Ribbon 的超时时间

Ribbon的超时时间可以通过如下配置项指定,默认情况下,这两项的值和feign的配置保持一致: 

<service-name>.ribbon.ConnectTimeout= <feign-default: 10000>
<service-name>.ribbon.ReadTimeout= <feign-default:6000>

其核心代码逻辑如下:

 IClientConfig getClientConfig(Request.Options options /*feign配置项*/, String clientName) {
        IClientConfig requestConfig;
        if (options == DEFAULT_OPTIONS) {
            requestConfig = this.clientFactory.getClientConfig(clientName);
        } else {
            requestConfig = new FeignOptionsClientConfig(options);
        }
        return requestConfig;
    }
       static class FeignOptionsClientConfig extends DefaultClientConfigImpl {
                //将Feign的配置设置为Ribbon的`IClientConfig`中
        public FeignOptionsClientConfig(Request.Options options) {
            setProperty(CommonClientConfigKey.ConnectTimeout,
                    options.connectTimeoutMillis());
            setProperty(CommonClientConfigKey.ReadTimeout, options.readTimeoutMillis());
        }

        @Override
        public void loadProperties(String clientName) {

        }

        @Override
        public void loadDefaultValues() {

        }

    }

Ribbon超时时间存在的意义
Ribbon的超时时间通过Feign配置项加载,构造其Ribbon客户端表示:IClientConfig,实际上该超时时间并没有实际使用的场景,仅仅作为配置项。
由上面的原则可以看出,当feign设置了超时时间,Ribbon会依据feign的设置同步。Ribbon的这个超时时间,用于指导真正调用接口时,设置真正实现者的超时时间

在没有Feign的环境下,Ribbon·和·Http Client客户端的关系
RibbonFeign是相对独立的组件,在一个Spring Cloud框架运行环境中,可以没有Feign。那么,在这种场景下,假设Http Client客户端使用的是OKHttp,并且通过ribbon.okhttp.enabled 指定ribbon调用时,会使用ribbon的超时配置来初始化OkHttp.代码如下所示:

@Configuration
@ConditionalOnProperty("ribbon.okhttp.enabled")
@ConditionalOnClass(name = "okhttp3.OkHttpClient")
public class OkHttpRibbonConfiguration {
   @RibbonClientName
   private String name = "client";

   @Configuration
   protected static class OkHttpClientConfiguration {
       private OkHttpClient httpClient;

       @Bean
       @ConditionalOnMissingBean(ConnectionPool.class)
       public ConnectionPool httpClientConnectionPool(IClientConfig config,
                                                      OkHttpClientConnectionPoolFactory connectionPoolFactory) {
                       
           RibbonProperties ribbon = RibbonProperties.from(config);
           int maxTotalConnections = ribbon.maxTotalConnections();
           long timeToLive = ribbon.poolKeepAliveTime();
           TimeUnit ttlUnit = ribbon.getPoolKeepAliveTimeUnits();
           return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
       }

       @Bean
       @ConditionalOnMissingBean(OkHttpClient.class)
       public OkHttpClient client(OkHttpClientFactory httpClientFactory,
                                  ConnectionPool connectionPool, IClientConfig config) {
           RibbonProperties ribbon = RibbonProperties.from(config);
           this.httpClient = httpClientFactory.createBuilder(false)
                       //使用Ribbon的超时时间来初始化OKHttp的 
                   .connectTimeout(ribbon.connectTimeout(), TimeUnit.MILLISECONDS)
                   .readTimeout(ribbon.readTimeout(), TimeUnit.MILLISECONDS)
                   .followRedirects(ribbon.isFollowRedirects())
                   .connectionPool(connectionPool)
                   .build();
           return this.httpClient;
       }

       @PreDestroy
       public void destroy() {
           if(httpClient != null) {
               httpClient.dispatcher().executorService().shutdown();
               httpClient.connectionPool().evictAll();
           }
       }
   }

Http Client的超时时间

 

为了保证整个组件调用链的超时关系,一般Spring Cloud采取的策略是:依赖方的超时配置覆盖被依赖方的配置
当然这个也不是绝对的,实际上对于Feign而言,可以直接指定FeignHttpClient之间的配置关系,如下所示:

@ConfigurationProperties(prefix = "feign.httpclient")
public class FeignHttpClientProperties {
    public static final boolean DEFAULT_DISABLE_SSL_VALIDATION = false;
    public static final int DEFAULT_MAX_CONNECTIONS = 200;
    public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 50;
    public static final long DEFAULT_TIME_TO_LIVE = 900L;
    public static final TimeUnit DEFAULT_TIME_TO_LIVE_UNIT = TimeUnit.SECONDS;
    public static final boolean DEFAULT_FOLLOW_REDIRECTS = true;
    public static final int DEFAULT_CONNECTION_TIMEOUT = 2000;
    public static final int DEFAULT_CONNECTION_TIMER_REPEAT = 3000;

    private boolean disableSslValidation = DEFAULT_DISABLE_SSL_VALIDATION;
        //连接池最大连接数,默认200
    private int maxConnections = DEFAULT_MAX_CONNECTIONS;
        //每一个IP最大占用多少连接 默认 50
    private int maxConnectionsPerRoute = DEFAULT_MAX_CONNECTIONS_PER_ROUTE;
        //连接池中存活时间,默认为5
    private long timeToLive = DEFAULT_TIME_TO_LIVE;
        //连接池中存活时间单位,默认为秒
    private TimeUnit timeToLiveUnit = DEFAULT_TIME_TO_LIVE_UNIT;
        //http请求是否允许重定向
    private boolean followRedirects = DEFAULT_FOLLOW_REDIRECTS;
        //默认连接超时时间:2000毫秒
    private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
        //连接池管理定时器执行频率:默认 3000毫秒
    private int connectionTimerRepeat = DEFAULT_CONNECTION_TIMER_REPEAT;

}

以 Http Client的实现OkHttp为例,如果指定了feign.okhttp.enabled,则会初始化Okhttp,其中,OkHttp的超时时间设置为:feign.httpclient.connectionTimeout,默认值为2000毫秒

@Configuration
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty(value = "feign.okhttp.enabled")
class OkHttpFeignLoadBalancedConfiguration {

    @Configuration
    @ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
    protected static class OkHttpFeignConfiguration {
        private okhttp3.OkHttpClient okHttpClient;

        @Bean
        @ConditionalOnMissingBean(ConnectionPool.class)
        public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
                                                       OkHttpClientConnectionPoolFactory connectionPoolFactory) {
            Integer maxTotalConnections = httpClientProperties.getMaxConnections();
            Long timeToLive = httpClientProperties.getTimeToLive();
            TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
            return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
        }

        @Bean
        public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
                                           ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
            Boolean followRedirects = httpClientProperties.isFollowRedirects();
            Integer connectTimeout = httpClientProperties.getConnectionTimeout();
            this.okHttpClient = httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation()).
                    connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).
                    followRedirects(followRedirects).
                    connectionPool(connectionPool).build();
            return this.okHttpClient;
        }

        @PreDestroy
        public void destroy() {
            if(okHttpClient != null) {
                okHttpClient.dispatcher().executorService().shutdown();
                okHttpClient.connectionPool().evictAll();
            }
        }
    }

    @Bean
    @ConditionalOnMissingBean(Client.class)
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                              SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
        OkHttpClient delegate = new OkHttpClient(okHttpClient);
        return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
    }
}

3. 最佳实践

有的同学可能觉得Spring Cloud 使用起来很方便,只需要引入一些组件即可。实际上,这正是Spring Cloud的坑所在的地方:因为它足够灵活,组件组装非常便捷,但是组件太多时,必须要有一个清晰的脉络去理清其间的关系
在整个组件配置组装的过程,超时设置遵循的基本原则是:依赖方的超时配置覆盖被依赖方的配置,而其配置覆盖的形式,则是使用的Spring Boot 的 AutoConfiguration 机制实现的。

综上所述,一般在Spring Cloud设置过程中,

  • 只需要指定Feign使用什么Http Client客户端即可,比如feign.okhttp.enabled=true
  • Feign客户端的Http Client的配置项,统一使用如下配置即可,Spring Cloud会拿才配置项初始化不同的Http Client客户端的。
### http client最大连接数,默认200
feign.httpclient.maxConnections = 200
### 每个IP路由最大连接数量
feign.httpclient.maxConnectionsPerRoute= 50
### 连接存活时间
feign.httpclient.timeToLive = 900
### 连接存活时间单位
feign.httpclient.timeToLiveUnit = SECONDS
### 连接超时时间
feign.httpclient.connectionTimeout = 2000
### 连接超时定时器的执行频率
fein.httpclient.connectionTimeout=3000
  • Hystrix的作用:Feign或者Http Client 只能规定所有接口调用的超时限制,而Hystrix可以设置到每一个接口的超时时间,控制力度最细,相对应地,配置会更繁琐。

Hystrix的超时时间和Feign或者Http Client的超时时间关系
Hystrix的超时意义是从代码执行时间层面控制超时;而FeignHttp Client 则是通过Http底层TCP/IP的偏网络层层面控制的超时。
我的建议是:一般情况下,Hystrix 的超时时间要大于FeignHttp Client的超时时间;而对于特殊需求的接口调用上,为了避免等待时间太长,需要将对应的Hystrix command 超时时间配置的偏小一点,满足业务侧的要求。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring Cloud的核心组件包括: 1. Eureka:服务注册和发现组件,用于管理服务的注册和发现。 2. Ribbon:客户端负载均衡组件,用于在多个服务提供者之间进行负载均衡。 3. Feign:声明式的REST客户端,用于简化服务之间的调用。 4. Hystrix:容错和断路器组件,用于处理服务之间的故障和超时。 5. Zuul:API网关组件,用于管理和路由服务请求。 6. Config:分布式配置中心,用于管理应用程序的配置信息。 7. Bus:消息总线组件,用于在分布式系统中传递消息。 8. Sleuth:分布式跟踪组件,用于跟踪服务之间的调用和请求。 ### 回答2: Spring Cloud是一个用于构建分布式系统的框架,它提供了一系列的核心组件来简化开发和管理分布式系统的复杂性。 Spring Cloud的核心组件包括: 1. Spring Cloud Netflix:这是Spring Cloud的最重要的组件之一,它集成了Netflix开源的一些工具和框架,例如Eureka、Ribbon、Hystrix等。这些工具和框架可以帮助开发人员构建可靠的、弹性的、高可用的分布式系统。 2. Spring Cloud Config:这个组件提供了集中式的配置管理,可以通过将配置文件存储在Git等版本控制工具中,动态地更新配置,避免了重新部署应用程序的麻烦。 3. Spring Cloud Gateway:这是一个高度可扩展的API网关,它可以将所有的外部请求路由到相应的服务进行处理。它可以提供负载均衡、安全认证等功能。 4. Spring Cloud Sleuth:这个组件可以追踪分布式系统的请求流程,提供了分布式跟踪的能力。通过集成日志、链路追踪等功能,可以更好地定位和解决分布式系统中的问题。 5. Spring Cloud Stream:这个组件可以简化数据流的处理。它提供了一种标准化的流式处理框架,使得开发人员可以更方便地实现消息传递、数据转换等功能。 6. Spring Cloud Bus:这个组件可以用于在分布式系统中传播状态的变化。通过使用消息代理(如RabbitMQ)来广播配置的更新,可以实现配置的动态刷新。 总之,Spring Cloud的核心组件为开发人员提供了一套完整的工具和框架来构建分布式系统。这些组件可以帮助开发人员解决分布式系统中的常见问题,实现弹性、高可用的系统架构。 ### 回答3: Spring Cloud是一个基于Spring框架的开源微服务框架。其核心组件有以下几个: 1. Spring Cloud Netflix:它是Spring Cloud中的基础组件,主要是对Netflix开源的一些组件进行封装,包括Eureka、Ribbon、Feign、Hystrix等。Eureka是一个服务注册与发现的组件,Ribbon是一个客户端负载均衡组件Feign是一个声明式的Web服务客户端,Hystrix是一个容错和延迟容错库,这些组件可以帮助开发者构建弹性和可靠的微服务架构。 2. Spring Cloud Config:用于集中管理和配置微服务的配置信息。它可以将配置信息存储在Git或其他后端存储库中,并通过服务端提供RESTful接口,供客户端获取配置信息。这样可以实现配置的动态更新和集中管理,方便配置的修改和维护。 3. Spring Cloud Bus:通过消息队列实现微服务之间的通信。它可以将配置信息的变更通过消息广播到所有的微服务实例,使得各个实例能够及时获取到新的配置信息,保持一致性。 4. Spring Cloud Sleuth:它用于分布式追踪微服务之间的请求和调用关系。通过为每个请求生成唯一的跟踪ID,并将该ID传递给下游服务,可以方便地追踪请求的链路。同时,它还集成了Zipkin等分布式追踪工具,可以可视化地展示和分析请求的调用链路,方便排查问题。 5. Spring Cloud Gateway:它是一个API网关,用于统一管理和路由微服务的请求。通过集中管理请求的入口和出口,可以对请求进行安全验证、流量控制、负载均衡、路由转发等操作,提供统一的API接口给客户端。 这些是Spring Cloud的核心组件,每个组件都有自己的功能和用途,共同构建了一个完善的微服务架构。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值