目录
FeignRibbonClientAutoConfiguration
1、FeignHttpClientProperties加载配置项
2、CachingSpringLoadBalancerFactory
1、HttpClientFeignLoadBalancedConfiguration
2、OkHttpFeignLoadBalancedConfiguration
3、DefaultFeignLoadBalancedConfiguration
随着Spring Boot项目的启动,会进行自动装配加载,当我们添加了spring-cloud-starter-openfeign启动maven依赖后,则会加载自动装配项如下:
则会自动装配FeignRibbonClientAutoConfiguration和FeignAutoConfiguration类型,但是在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:HasFeatures和FeignContext,特别是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;
}
}