起因

最近想通过配置feign参数,来改变feign客户端之间connectTimeout和readTimeout。

feign参数配置

feign:
  client:
    config:
      default:
        #不设置connectTimeout会导致readTimeout设置不生效
        connectTimeout: 3000
        readTimeout: 6000 

发现了两个问题

1 只设置单独的connectTimeout或者readTimeout时不生效,必须两个值都设置才行
2 发现feign默认的connectTimeout时长是10s,readTimeout时长是60s。但是实际服务之间调用readTimeout超过1秒就会超时。

问题一排查

首先看入口类FeignAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
		FeignHttpClientProperties.class })
@Import(DefaultGzipDecoderConfiguration.class)
public class FeignAutoConfiguration {

配置文件对应类FeignClientProperties

@ConfigurationProperties("feign.client")
public class FeignClientProperties {

排查发现feign是通过FeignClientProperties来接收配置文件中的配置的。

FeignClientFactoryBean类用于读取配置参数来进行feign配置

protected void configureUsingProperties(
			FeignClientProperties.FeignClientConfiguration config,
			Feign.Builder builder) {
		if (config == null) {
			return;
		}

		if (config.getLoggerLevel() != null) {
			builder.logLevel(config.getLoggerLevel());
		}

		if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
			builder.options(new Request.Options(config.getConnectTimeout(),
					config.getReadTimeout()));
		}

		if (config.getRetryer() != null) {
			Retryer retryer = getOrInstantiate(config.getRetryer());
			builder.retryer(retryer);
		}

		if (config.getErrorDecoder() != null) {
			ErrorDecoder errorDecoder = getOrInstantiate(config.getErrorDecoder());
			builder.errorDecoder(errorDecoder);
		}

跟踪configureUsingProperties方法最终发现了问题一产生的原因。

问题二排查

基于问题一中configureUsingProperties方法去跟踪Request.Options的默认值发现如下

   /**
     * Creates the new Options instance using the following defaults:
     * <ul>
     * <li>Connect Timeout: 10 seconds</li>
     * <li>Read Timeout: 60 seconds</li>
     * <li>Follow all 3xx redirects</li>
     * </ul>
     */
    public Options() {
      this(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true);
    }
    /**
     * Creates a new Options instance.
     *
     * @param connectTimeoutMillis connection timeout in milliseconds.
     * @param readTimeoutMillis read timeout in milliseconds.
     * @param followRedirects if the request should follow 3xx redirections.
     *
     * @deprecated please use {@link #Options(long, TimeUnit, long, TimeUnit, boolean)}
     */
    @Deprecated
    public Options(int connectTimeoutMillis, int readTimeoutMillis, boolean followRedirects) {
      this(connectTimeoutMillis, TimeUnit.MILLISECONDS,
          readTimeoutMillis, TimeUnit.MILLISECONDS,
          followRedirects);
    }

其默认的connectTimeout时长是10s,readTimeout时长是60s。很奇怪为啥我的项目1秒就超时呢??

LoadBalancerFeignClient

该类用于处理feign客户端的http请求,跟断点进来的。具体咋找到这这里我也记不清了,总之很曲折。

@Override
	public Response execute(Request request, Request.Options options) throws IOException {
		try {
			URI asUri = URI.create(request.url());
			String clientName = asUri.getHost();
			URI uriWithoutHost = cleanUrl(request.url(), clientName);
			FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
					this.delegate, request, uriWithoutHost);

			IClientConfig requestConfig = getClientConfig(options, clientName);
			return lbClient(clientName)
					.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
		}
		catch (ClientException e) {
			IOException io = findIOException(e);
			if (io != null) {
				throw io;
			}
			throw new RuntimeException(e);
		}
	}
//获取feign的方法继续跟这里到【this.clientFactory.getClientConfig】	
IClientConfig getClientConfig(Request.Options options, String clientName) {
		IClientConfig requestConfig;
		if (options == DEFAULT_OPTIONS) {
			requestConfig = this.clientFactory.getClientConfig(clientName);
		}
		else {
			requestConfig = new FeignOptionsClientConfig(options);
		}
		return requestConfig;
	}

可以看到当options没有被修改过时,feign的配置信息会通过clientFactory(SpringClientFactory)获取。这里可以自己打个断点,可以看到clientFactory.getClientConfig(xxx)返回的配置信息中connectTimeout和readTimeout都只有1秒,到此feign的两个问题已经找到了答案。

SpringClientFactory

延申下clientFactory对象其实就是SpringClientFactory,该类作用:创建客户端、负载均衡器和客户端配置实例的工厂。 它为每个客户端名称创建一个 Spring ApplicationContext,并从那里提取它需要的 bean。个人理解就是每个feign客户端在服务端中都有一个独立的子容器。这块还有待学习。

/**
	 * Get the client config associated with the name.
	 * @param name name to search by
	 * @return {@link IClientConfig} instance
	 * @throws RuntimeException if any error occurs
	 */
	public IClientConfig getClientConfig(String name) {
		return getInstance(name, IClientConfig.class);
	}
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐