定制小程序开发费用Spring Cloud Open Feign系列【8】Feign超时配置详解及源码分析

文章目录

前言

定制小程序开发费用在之前的文档中,介绍Ribbion定制小程序开发费用原理及基本使用,定制小程序开发费用接下来介绍下其他的一定制小程序开发费用些配置使用。

官网案例

在中,定制小程序开发费用有一个客户端的配置文件,定制小程序开发费用这里面就包含了Ribbion 定制小程序开发费用的常用配置项。

# 定制小程序开发费用同一服务上的最大重试次数(定制小程序开发费用不包括第一次重试))sample-client.ribbon.MaxAutoRetries=1# 定制小程序开发费用要重试的下一台服务的最大数量(定制小程序开发费用不包括第一台服务)sample-client.ribbon.MaxAutoRetriesNextServer=1# 是否可以重试此客户端的所有操作sample-client.ribbon.OkToRetryOnAllOperations=true# 刷新服务列表的时间间隔sample-client.ribbon.ServerListRefreshInterval=2000# Http 客户端连接超时时间sample-client.ribbon.ConnectTimeout=3000# Http 客户端读取超时时间sample-client.ribbon.ReadTimeout=3000# 服务初始列表sample-client.ribbon.listOfServers=www.microsoft.com:80,www.yahoo.com:80,www.google.com:80
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

客户端配置的格式为:

<clientName>.<nameSpace>.<propertyName>=<value>
  • 1

各项说明如下:

  • clientName(客户端名称) :也就是对应@FeignClient注解中的名称,Feign 会使用这个名称来标识每一个Http客户端。
  • nameSpace (命名空间)是可配置的,默认情况下是“ribbon”
  • propertyName(属性名): 所有的配置属性可以在CommonClientConfigKey类中查看
  • value(值):配置属性对应的值

如果配置了clientName,则表示这是一个局部配置,只作用于当前客户端,如果没有配置clientName,则适用于所有客户端的属性(也就是全局配置)。

例如以下配置表示,为所有客户端设置默认的 ReadTimeout 属性。

ribbon.ReadTimeout=1000
  • 1

连接超时和读取超时配置

在配置中,有一个ConnectTimeoutReadTimeout,这是在发送请求时的基础配置,特别重要,所以接下来分析下这两个具体是干嘛的,源码是怎么处理的。

参数说明

ConnectTimeout连接超时时间,Feign 是基于HTTP 的远程调用,众所周知,HTTP 请求会进行TCP的三次握手,这个连接超时时间,就是多少秒没连接上,就会抛出超时异常。

ReadTimeout读取超时时间,HTTP成功连接后,客户端发会送请求报文,服务端收到后解析并返回响应报文,在写出响应报文时,如果超过了设置的时间还没写完,也会抛出超时异常。在某些接口请求数据量大的时候,很容易出现读取超时,所以要格外注意这个问题。

可以在RibbonClientConfiguration配置类中看到,客户端超时配置默认都是1秒,所以不自己改配置的话,很容易造成超时问题。

配置案例

在订单服务中,让线程睡眠十秒才返回响应。

访问账户服务,发现1秒左右就马上抛出超时异常了。

支持在ribbon 或者feign配置项下配置,feign 下配置优先级最高,而且最新版已经移除了,所以推荐配置在feign中。

1、在ribbon 中配置
ribbon命名空间下添加配置,将会作用于所有客户端。

ribbon:  ConnectTimeout: 5000  ReadTimeout: 12000
  • 1
  • 2
  • 3

可以为某个单独的客户端配置不同的超时配置,配置前缀为客户端名称。

order-service:  ribbon:    ConnectTimeout: 6000    ReadTimeout: 13000
  • 1
  • 2
  • 3
  • 4

2、在feign 中配置
也可以在feign 下配置,default 表示作用于所有客户端,也可替换default 为客户端名称,表示作用于单个客户端。

feign:  okhttp:    enabled: true  client:    config:      default:        ConnectTimeout: 6000        ReadTimeout: 13000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

源码分析

1. 启动项目

那么这些参数是怎么加载,最后作用到哪里了呢,接下来以Feign 下配置超时时间,分析下源码。

Feign通过接口生成代理对象,扫描到Feign 接口,构建代理对象,在Feign.builder()创建构建者时,会完成客户端的初始化配置。在这个时候会创建一个Options对象。

        public Builder() {        	// 日志级别            this.logLevel = Level.NONE;            this.contract = new Default();            this.client = new feign.Client.Default((SSLSocketFactory)null, (HostnameVerifier)null);            this.retryer = new feign.Retryer.Default();            this.logger = new NoOpLogger();            // 编码解码器            this.encoder = new feign.codec.Encoder.Default();            this.decoder = new feign.codec.Decoder.Default();            this.queryMapEncoder = new FieldQueryMapEncoder();            this.errorDecoder = new feign.codec.ErrorDecoder.Default();            //             this.options = new Options();            this.invocationHandlerFactory = new feign.InvocationHandlerFactory.Default();            this.closeAfterDecode = true;            this.propagationPolicy = ExceptionPropagationPolicy.NONE;            this.forceDecoding = false;            this.capabilities = new ArrayList();        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

Options对象封装了超时时间,构造方法初始化的超时时间分别为10S、60S,这是Feign 原生框架的配置,但是会被覆盖。

        public Options() {            this(10L, TimeUnit.SECONDS, 60L, TimeUnit.SECONDS, true);        }
  • 1
  • 2
  • 3

代理对象生成时,会初始化方法处理器,这里又会为每个方法设置Options对象,这里的Options就是加载我们配置的超时参数了。

    private SynchronousMethodHandler(Target<?> target, Client client, Retryer retryer, List<RequestInterceptor> requestInterceptors, Logger logger, Level logLevel, MethodMetadata metadata, feign.RequestTemplate.Factory buildTemplateFromArgs, Options options, Decoder decoder, ErrorDecoder errorDecoder, boolean decode404, boolean closeAfterDecode, ExceptionPropagationPolicy propagationPolicy, boolean forceDecoding) {        this.target = (Target)Util.checkNotNull(target, "target", new Object[0]);        this.client = (Client)Util.checkNotNull(client, "client for %s", new Object[]{target});        // 省略.....        this.options = (Options)Util.checkNotNull(options, "options for %s", new Object[]{target});        // 省略.....        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2. 执行流程

之前我们分析过,客户端的上下文及配置,是在其第一次访问时才会进行加载。

Feign 接口方法执行时,实际是SynchronousMethodHandler的invoke 方法代理执行,在该方法中会完成请求模板创建、参数解析、重试机制加载。该处理器会查询方法参数中是否有Options 对象,没有则会将初始化加载的超时配置,传递到下游。

    public Object invoke(Object[] argv) throws Throwable {    	// 1. 构建请求模板,封装参数、路径等信息。        RequestTemplate template = this.buildTemplateFromArgs.create(argv);        // 2. 查询超时配置,将方法的参数集合转为Stream 流,如果没有发现参数中有Options 对象,        // 则会使用方式执行器中的Options ,也就是从yml 中加载的配置        Options options = this.findOptions(argv);        Retryer retryer = this.retryer.clone();        while(true) {            try {                return this.executeAndDecode(template, options);            } catch (RetryableException var9) {               // 省略....        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

继续走到负载均衡客户端(Ribbon),可以看到这里又会去获取一次客户端配置。

getClientConfig 方法中,会处理超时配置Options对象。

    IClientConfig getClientConfig(Options options, String clientName) {        Object requestConfig;        // 查看Options 是否默认的,也就是是否是1秒。        if (options == DEFAULT_OPTIONS) {        	// 是默认的,则直接加载IClientConfig (容器中)对象中的配置            requestConfig = this.clientFactory.getClientConfig(clientName);        } else {        	// 是自定义了超时配置,则设置自定义配置到IClientConfig(自己创建)对象中            requestConfig = new LoadBalancerFeignClient.FeignOptionsClientConfig(options);        }        return (IClientConfig)requestConfig;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

可以看到,负载均衡器客户端获取到了自定义配置,然后继续往下走。

走到执行方法:

return ((RibbonResponse)this.lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();
  • 1

负载均衡器会调用本身的this.lbClient(clientName) 方法,调用工厂创建一个负载均衡器FeignLoadBalancer,会查询缓存,没有则会创建一个并放入缓存。

    public FeignLoadBalancer create(String clientName) {    	// 缓存查询        FeignLoadBalancer client = (FeignLoadBalancer)this.cache.get(clientName);        if (client != null) {            return client;        } else {        	// 没有则又会查询一次客户端配置,直接查询`IClientConfig `Bean 对象        	// 在自动配置类RibbonClientConfiguration中, 超时配置都是1秒。            IClientConfig config = this.factory.getClientConfig(clientName);            ILoadBalancer lb = this.factory.getLoadBalancer(clientName);            ServerIntrospector serverIntrospector = (ServerIntrospector)this.factory.getInstance(clientName, ServerIntrospector.class);            FeignLoadBalancer client = this.loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector, this.loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);            this.cache.put(clientName, client);            return (FeignLoadBalancer)client;        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

可以看到,创建的FeignLoadBalancer对象中,超时配置,又到了默认的一秒。

接着调用FeignLoadBalancer对象的executeWithLoadBalancer方法,均衡器开始执行,参数是一个Ribbon请求对象和请求配置IClientConfig对象(因为有自定义,所以这里是重新创建的,并不是容器中的)。

在通过均衡算法,获取到真实的服务地址后,进入到execute 方法,该方法传入了可用服务和 请求配置IClientConfig对象(重新创建的)。

在execute 方法可以看到,又有对Options进行一次判断。该请求存在自定义超时配置,则会解析并封装为Options,没有配置,则使用默认配置(1秒)。

    public FeignLoadBalancer.RibbonResponse execute(FeignLoadBalancer.RibbonRequest request, IClientConfig configOverride) throws IOException {    	// 这次请求的配置参数        Options options;        // 发现当前请求存在客户端配置        if (configOverride != null) {        	// 将配置中的自定义超时 解析,并封装为Options 对象            RibbonProperties override = RibbonProperties.from(configOverride);            options = new Options(override.connectTimeout(this.connectTimeout), override.readTimeout(this.readTimeout));        } else {        	// 没有配置,则默认使用Ribbon 客户端配置,而Ribbon 又是从`IClientConfig `Bean 对象获取的,默认都是一秒。            options = new Options(this.connectTimeout, this.readTimeout);        }        Response response = request.client().execute(request.toRequest(), options);        return new FeignLoadBalancer.RibbonResponse(request.getUri(), response);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

最终均衡器,会调用HTTP 客户端进行请求发送,这里使用的是OkHttpClient 。这里会覆盖掉OkHttpClient 超时配置,使用自定义或者默认的超时配置(所以在OkHttp中的配置超时没有啥用…)。

    public Response execute(feign.Request input, Options options) throws IOException {        okhttp3.OkHttpClient requestScoped;        // 查看OkHttp 的超时配置是否和 Feign 配置的超时一样        // 一样则不处理。        if (this.delegate.connectTimeoutMillis() == options.connectTimeoutMillis() && this.delegate.readTimeoutMillis() == options.readTimeoutMillis() && this.delegate.followRedirects() == options.isFollowRedirects()) {            requestScoped = this.delegate;        } else {        	// 不一样,则重新构建一个 OkHttpClient ...(这里是否有优化空间,Ribbon 会覆盖OkHttpClient 配置)            requestScoped = this.delegate.newBuilder().connectTimeout((long)options.connectTimeoutMillis(), TimeUnit.MILLISECONDS).readTimeout((long)options.readTimeoutMillis(), TimeUnit.MILLISECONDS).followRedirects(options.isFollowRedirects()).build();        }        Request request = toOkHttpRequest(input);        // 执行请求        okhttp3.Response response = requestScoped.newCall(request).execute();        return toFeignResponse(response, input).toBuilder().request(input).build();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

请求发送以后,如果触发了超时时间,就会抛出超时异常,所有Feign 超时配置,最后是作用到了底层的HTTP 框架。

3. 总结

  1. Feign 原生构建客户端,超时时间是10S、60S,但是没有用到。
  2. 方法处理器在加载时,会获取到自定义配置。
  3. 第一次加载时,客户端配置类IClientConfig注入到了IOC中,默认超时都是1S。
  4. 请求执行时,会构建超时配置类Options,如果存在自定义配置,就会使用自定义配置创建Options对象,并将该对象传递给HTTP 客户端框架。
  5. HTTP 客户端 会判断自身设置的超时时间和Feign 设置的是否相同,不同则会重新创建一个客户端请求,一样则会使用Feign代理的客户端(所以需要注意HTTP 框架和Feign 的超时要设置一样,不然重新创建会消耗资源)。
网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发