Spring Cloud Feign源 FeignRibbonClientAutoConfiguration自动装配

2022年6月3日13:09:44

目录

FeignRibbonClientAutoConfiguration

1、FeignHttpClientProperties加载配置项

2、CachingSpringLoadBalancerFactory

3、配置默认的@FeignClient的连接和调用超时时间

Feign客户端实现

1、HttpClientFeignLoadBalancedConfiguration

2、OkHttpFeignLoadBalancedConfiguration

3、DefaultFeignLoadBalancedConfiguration

FeignAutoConfiguration


    随着Spring Boot项目的启动,会进行自动装配加载,当我们添加了spring-cloud-starter-openfeign启动maven依赖后,则会加载自动装配项如下:

    则会自动装配FeignRibbonClientAutoConfigurationFeignAutoConfiguration类型,但是在FeignRibbonClientAutoConfiguration的类注解上则有@AutoConfigureBefore(FeignAutoConfiguration.class),即优于FeignAutoConfiguration进行加载。

FeignRibbonClientAutoConfiguration

// 1、ILoadBalancer和Feign类存在才加载该Bean FeignRibbonClientAutoConfiguration
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
// 2、当前的FeignRibbonClientAutoConfiguration先于FeignAutoConfiguration加载
@AutoConfigureBefore(FeignAutoConfiguration.class)
// 3、加载配置FeignHttpClientProperties的属性
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
// 4、有顺序的加载HttpClient、okhttp类型(前提【都】是引入了包和启动配置)、最后优先级是加载默认项,后面详细分析该部分
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
		OkHttpFeignLoadBalancedConfiguration.class,
		DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {

	// 5、加载CachingSpringLoadBalancerFactory类型Bean,前提是没有类RetryTemplate,该需要单独引入Spring-retry的maven依赖
	@Bean
	@Primary
	@ConditionalOnMissingBean
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	public CachingSpringLoadBalancerFactory cachingLBClientFactory(
			SpringClientFactory factory) {
		return new CachingSpringLoadBalancerFactory(factory);
	}

	// 6、加载CachingSpringLoadBalancerFactory类型Bean,前提是存在类RetryTemplate,该需要单独引入Spring-retry的maven依赖
	@Bean
	@Primary
	@ConditionalOnMissingBean
	@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
	public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
			SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
		return new CachingSpringLoadBalancerFactory(factory, retryFactory);
	}

	// 7、加载全局的默认@FeignClient的连接和读取超时配置
	@Bean
	@ConditionalOnMissingBean
	public Request.Options feignRequestOptions() {
		return LoadBalancerFeignClient.DEFAULT_OPTIONS;
	}
}
1、ILoadBalancer和Feign类存在才加载该Bean FeignRibbonClientAutoConfiguration
2、当前的FeignRibbonClientAutoConfiguration先于FeignAutoConfiguration加载
3、加载配置FeignHttpClientProperties的属性
4、有顺序的加载HttpClient、okhttp类型(前提【都】是引入了包和启动配置)、优先级最低的是加载默认项
5、加载CachingSpringLoadBalancerFactory类型Bean,前提是没有类RetryTemplate,该需要单独引入Spring-retry的maven依赖
6、加载CachingSpringLoadBalancerFactory类型Bean,前提是存在类RetryTemplate,该需要单独引入Spring-retry的maven依赖
7、加载全局的默认@FeignClient的连接和读取超时配置

1、FeignHttpClientProperties加载配置项

@ConfigurationProperties(prefix = "feign.httpclient")
public class FeignHttpClientProperties {

}

    加载http client相关的配置项,需要配置后面的HttpClientFeignLoadBalancedConfiguration进行分析。

2、CachingSpringLoadBalancerFactory

    缓存的负载均衡调用工厂非常重要,他提供了两个构造函数,SpringClientFactory类型,以及看我们是否添加了Spring-Retry的maven依赖,有则可以对服务的调用增加失败重试机制,否则直接走降级。所以当前使用的是构造函数依赖注入的方式,则需要关注在其他地方注入的SpringClientFactory类型的Bean。使用的地方后续服务调用时再分析。

3、配置默认的@FeignClient的连接和调用超时时间

@Bean
@ConditionalOnMissingBean
public Request.Options feignRequestOptions() {
    return LoadBalancerFeignClient.DEFAULT_OPTIONS;
}

    如果其他地方没有注入该值,即我们没有使用yml、properties设置超时时间则会为服务调用设置超时时间,并且FeignClientFactoryBean#getObject过程会发现,配置项适用于所有的@FeignClient项。

public class LoadBalancerFeignClient implements Client {

	static final Request.Options DEFAULT_OPTIONS = new Request.Options();

    public static class Options {

    private final int connectTimeoutMillis;
    private final int readTimeoutMillis;
    private final boolean followRedirects;

    public Options(int connectTimeoutMillis, int readTimeoutMillis, boolean followRedirects) {
      this.connectTimeoutMillis = connectTimeoutMillis;
      this.readTimeoutMillis = readTimeoutMillis;
      this.followRedirects = followRedirects;
    }

    public Options(int connectTimeoutMillis, int readTimeoutMillis) {
      this(connectTimeoutMillis, readTimeoutMillis, true);
    }

    public Options() {
      this(10 * 1000, 60 * 1000);
    }
}

    所以默认的链接超时是1秒,读取超时是60秒

Feign客户端实现

 先看看Feign的Client【客户端】即真正调用远程服务的接口:

public interface Client {

  Response execute(Request request, Options options) throws IOException;
}

    定义了真的调用的Http请求的过程,其中Options类型,上面已经见过了,配置了超时时间。其子类按照优先级有:

ApacheHttpClient:优先级最高使用的是Apache的HttpClient线程池实现,前提是引入feign-httpclient依赖

OkHttpClient:使用OkHttp线程池实现,需要引入feign-okhttp依赖

Client.Default:使用默认的HttpURLConnnection连接池,但是性能比较低,特别是访问路径中存在{}占位符等

LoadBalancerFeignClient:使用装饰器模式,对上面的类型进行包装,因为不论是否那种方式的连接池,最后调用服务时都需要负载均衡策略

还是从上面的注解开始:

@Import({ HttpClientFeignLoadBalancedConfiguration.class,
		OkHttpFeignLoadBalancedConfiguration.class,
		DefaultFeignLoadBalancedConfiguration.class })

1、HttpClientFeignLoadBalancedConfiguration

    对应上面的ApacheHttpClient类型客户端,需要引入maven依赖,如:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
    <version>9.5.0</version>
</dependency>
@Configuration
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
class HttpClientFeignLoadBalancedConfiguration {

	@Bean
	@ConditionalOnMissingBean(Client.class)
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
							  SpringClientFactory clientFactory, HttpClient httpClient) {
		ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
		return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
	}

	@Configuration
	@ConditionalOnMissingBean(CloseableHttpClient.class)
	protected static class HttpClientFeignConfiguration {

		private final Timer connectionManagerTimer = new Timer(
				"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);

		private CloseableHttpClient httpClient;

		@Autowired(required = false)
		private RegistryBuilder registryBuilder;

		@Bean
		@ConditionalOnMissingBean(HttpClientConnectionManager.class)
		public HttpClientConnectionManager connectionManager(
				ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
				FeignHttpClientProperties httpClientProperties) {
			final HttpClientConnectionManager connectionManager = connectionManagerFactory
					.newConnectionManager(httpClientProperties.isDisableSslValidation(),
							httpClientProperties.getMaxConnections(),
							httpClientProperties.getMaxConnectionsPerRoute(),
							httpClientProperties.getTimeToLive(),
							httpClientProperties.getTimeToLiveUnit(),
							this.registryBuilder);
			this.connectionManagerTimer.schedule(new TimerTask() {
				@Override
				public void run() {
					connectionManager.closeExpiredConnections();
				}
			}, 30000, httpClientProperties.getConnectionTimerRepeat());
			return connectionManager;
		}

		@Bean
		@ConditionalOnProperty(value = "feign.compression.response.enabled", havingValue = "true")
		public CloseableHttpClient customHttpClient(
				HttpClientConnectionManager httpClientConnectionManager,
				FeignHttpClientProperties httpClientProperties) {
			HttpClientBuilder builder = HttpClientBuilder.create()
					.disableCookieManagement().useSystemProperties();
			this.httpClient = createClient(builder, httpClientConnectionManager,
					httpClientProperties);
			return this.httpClient;
		}

		@Bean
		@ConditionalOnProperty(value = "feign.compression.response.enabled", havingValue = "false", matchIfMissing = true)
		public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
											  HttpClientConnectionManager httpClientConnectionManager,
											  FeignHttpClientProperties httpClientProperties) {
			this.httpClient = createClient(httpClientFactory.createBuilder(),
					httpClientConnectionManager, httpClientProperties);
			return this.httpClient;
		}

		private CloseableHttpClient createClient(HttpClientBuilder builder,
												 HttpClientConnectionManager httpClientConnectionManager,
												 FeignHttpClientProperties httpClientProperties) {
			RequestConfig defaultRequestConfig = RequestConfig.custom()
					.setConnectTimeout(httpClientProperties.getConnectionTimeout())
					.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
					.build();
			CloseableHttpClient httpClient = builder
					.setDefaultRequestConfig(defaultRequestConfig)
					.setConnectionManager(httpClientConnectionManager).build();
			return httpClient;
		}

		@PreDestroy
		public void destroy() throws Exception {
			this.connectionManagerTimer.cancel();
			if (this.httpClient != null) {
				this.httpClient.close();
			}
		}

	}
}

1、不仅需要引入上面的maven依赖,才@ConditionalOnClass(ApacheHttpClient.class)能注入

2、需要配置属性feign.httpclient.enabled

    @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)

3、构造器依赖于上面注入的CachingSpringLoadBalancerFactory,SpringClientFactory、HttpClient类型Bean,注入返回装饰ApacheHttpClient类型的LoadBalancerFeignClient客户端

4、创建连接器管理器HttpClientConnectionManager,虽然设置了一些超市等参数信息,并且使用到了上面从配置文件中加载的FeignHttpClientProperties进行构造依赖注入,但是我们看看真正的管理器实现类为PoolingHttpClientConnectionManager

public PoolingHttpClientConnectionManager(
		final HttpClientConnectionOperator httpClientConnectionOperator,
		final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory,
		final long timeToLive, final TimeUnit timeUnit) {
	super();
	this.configData = new ConfigData();
	this.pool = new CPool(new InternalConnectionFactory(
			this.configData, connFactory), 2, 20, timeToLive, timeUnit);
	this.pool.setValidateAfterInactivity(2000);
	this.connectionOperator = Args.notNull(httpClientConnectionOperator, "HttpClientConnectionOperator");
	this.isShutDown = new AtomicBoolean(false);
}

那么底层真实的线程池参数,则不满足并发量比较高的情况,所以一般建议我们自己显示进行设置,也方便后续进行参数修改,一般需要上线后与其他使用自定义线程池一样进行压测,设置最合理的参数信息。

@Bean(destroyMethod = "close")
public CloseableHttpClient httpClient() {
	PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
	connectionManager.setMaxTotal(400);
	connectionManager.setDefaultMaxPerRoute(100);

	RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(2000)//从连接池获取连接等待超时时间
			.setConnectTimeout(2000)//请求超时时间
			.setSocketTimeout(15000)//等待服务响应超时时间
			.build();
	HttpClientBuilder httpClientBuilder = HttpClientBuilder.create().setConnectionManager(connectionManager)
			.setDefaultRequestConfig(requestConfig)
			//自定义重试策略,针对502和503重试一次
			.setServiceUnavailableRetryStrategy(new CustomizedServiceUnavailableRetryStrategy())
			.evictExpiredConnections();
	return httpClientBuilder.build();
}

2、OkHttpFeignLoadBalancedConfiguration

     对应上面的OkHttpClient类型客户端,需要引入maven依赖,如:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
    <version>10.2.0</version>
</dependency>
@Configuration
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
class OkHttpFeignLoadBalancedConfiguration {

	@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);
	}

	@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 (this.okHttpClient != null) {
				this.okHttpClient.dispatcher().executorService().shutdown();
				this.okHttpClient.connectionPool().evictAll();
			}
		}

	}
}

1、不仅需要引入上面的maven依赖,才@ConditionalOnClass(OkHttpClient.class)能注入

2、需要配置属性feign.okhttp.enabled

    @ConditionalOnProperty("feign.okhttp.enabled")

3、构造器依赖于上面注入的CachingSpringLoadBalancerFactory,SpringClientFactory、HttpClient类型Bean,注入返回装饰OkHttpClient类型的LoadBalancerFeignClient客户端

4、构造器依赖注入返回连接器ConnectionPool类型,也使用到了上面配置的FeignHttpClientProperties属性,还有依赖到了OkHttpClientConnectionPoolFactory类型,全局查找发现是从spring-cloud-commons包中的HttpClientConfiguration中进行配置的,返回了DefaultOkHttpClientConnectionPoolFactory类型。

public class DefaultOkHttpClientConnectionPoolFactory
		implements OkHttpClientConnectionPoolFactory {

	@Override
	public ConnectionPool create(int maxIdleConnections, long keepAliveDuration,
			TimeUnit timeUnit) {
		return new ConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit);
	}
}

直接new的对象,设置了连接的最大数,超时等参数,但是线程池是对象中默认写死的,入下:

public final class ConnectionPool {

  private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
}

    直接使用了juc原生的线程池,核心线程为0,最大线程为Integer最大值,并且使用了无界队列SynchronousQueue【入队需要有其他任务出队】,即maxIdleConnections最大连接数参数就很关键了,需要小心参数。

3、DefaultFeignLoadBalancedConfiguration

    不满足上面的条件,则会注入默认的Feign Client,如下:

@Configuration
class DefaultFeignLoadBalancedConfiguration {
	@Bean
	@ConditionalOnMissingBean
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
							  SpringClientFactory clientFactory) {
		return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
				clientFactory);
	}
}

FeignAutoConfiguration

    由于上面的优先级高于该自动装配项,大多的装配以上面为准,但是这里装配了两个比较重要的Bean:HasFeaturesFeignContext,特别是FeignContext会在后面FeignClientFactoryBean#getObject中使用到。

public class FeignAutoConfiguration {

	@Autowired(required = false)
	private List<FeignClientSpecification> configurations = new ArrayList<>();

	@Bean
	public HasFeatures feignFeature() {
		return HasFeatures.namedFeature("Feign", Feign.class);
	}

	@Bean
	public FeignContext feignContext() {
		FeignContext context = new FeignContext();
		context.setConfigurations(this.configurations);
		return context;
	}
}

  • 作者:it_lihongmin
  • 原文链接:https://blog.csdn.net/it_lihongmin/article/details/109037892
    更新时间:2022年6月3日13:09:44 ,共 13588 字。