Spring Cloud Feign源 FeignRibbonClientAutoConfiguration自动装配

目录

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