1.现状
目前所有应用调用用户中台采用feign接口,采用httpclient并且使用的是默认配置,目前主要的默认配置参数,参数所配置的超时时间太长,会导致大企业耗时比较长的接口会耗光所有资源(这些接口本身就需要优化,加分页等),导致其他企业的功能受到影响,所以需要对这些参数进行优化:
基本配置
connectTimeout 连接超时时间 10s
readTimeout 从服务器读取到资源所用时间 60s
automaticRetriesDisabled 是否取消重试 false,允许重试,重试次数默认3次
连接池配置
maxConnections 总的最大连接数 50
maxConnectionsPerRoute 每个路由的最大连接数 20(实际采用这个字段)
修改后的配置:
基本配置
connectTimeout 连接超时时间 3s
readTimeout 从服务器读取到资源所用时间 5s
automaticRetriesDisabled 是否取消重试 true,不重试
连接池配置
maxConnections 总的最大连接数 200
maxConnectionsPerRoute 每个路由的最大连接数 50(实际采用这个字段)
2.修改方案
feign配置httpclient在FeignAutoConfiguration类中,相关源码:
@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(), registryBuilder);
this.connectionManagerTimer.schedule(new TimerTask() {
@Override
public void run() {
connectionManager.closeExpiredConnections();
}
}, 30000, httpClientProperties.getConnectionTimerRepeat());
return connectionManager;
}
@Bean
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
HttpClientConnectionManager httpClientConnectionManager,
FeignHttpClientProperties httpClientProperties) {
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
.build();
this.httpClient = httpClientFactory.createBuilder().
setConnectionManager(httpClientConnectionManager).
setDefaultRequestConfig(defaultRequestConfig).build();
return this.httpClient;
}
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(HttpClient httpClient) {
return new ApacheHttpClient(httpClient);
}
主要有三部分:
HttpClientConnectionManager 连接池管理器配置,主要是对连接池相关参数的配置
CloseableHttpClient httpclient相关配置,包括连接超时时间,读取超时时间等参数
ApacheHttpClient feign自定义的扩展Client,用包装器模式包装httpclient
所以如果需要修改feign上述的相关参数,就需要同时修改HttpClientConnectionManager,CloseableHttpClient和ApacheHttpClient,所以在AppWebConfig中可以增加如下代码:
/**
* 设置feign连接池
* @return
*/
@Bean("feignHttpClientConnectionManager")
public HttpClientConnectionManager httpClientConnectionManager(){
DefaultApacheHttpClientConnectionManagerFactory connectionManagerFactory =
new DefaultApacheHttpClientConnectionManagerFactory();
final HttpClientConnectionManager connectionManager = connectionManagerFactory
.newConnectionManager(false, 200,
50, 900, TimeUnit.SECONDS, null);
/**
* 关闭失效链接
*/
ScheduledExecutorService scheduledExecutorService =
new ScheduledThreadPoolExecutor(1);
scheduledExecutorService.scheduleWithFixedDelay(()->connectionManager.closeExpiredConnections(), 30,10, TimeUnit.SECONDS);
return connectionManager;
}
/**
* 设置feign接口httpclient配置
* @return
*/
@Bean("feignHttpClient")
public Client httpClient(HttpClientConnectionManager feignHttpClientConnectionManager) {
/**
* 设置参数
* connectTimeout 3000ms
* connectionRequestTimeout 1000ms
* socketTimeout 5000ms
*/
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(3000)
.setConnectionRequestTimeout(1000)
.setRedirectsEnabled(true)
.setSocketTimeout(5000)
.build();
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
DefaultApacheHttpClientFactory httpClientFactory = new DefaultApacheHttpClientFactory(httpClientBuilder);
/**
* 取消重试
*/
CloseableHttpClient httpClient = httpClientFactory.createBuilder().
setConnectionManager(feignHttpClientConnectionManager).
setDefaultRequestConfig(defaultRequestConfig)
.disableAutomaticRetries()
.build();
return new ApacheHttpClient(httpClient);
}
修改代码后进行测试发现,其中的ConnectTimeout和socketTimeout并没有生效,使用的还是系统默认值,分别为10和60s,追踪源码发现
(InternalHttpClient类):
final HttpRequestWrapper wrapper = HttpRequestWrapper.wrap(request, target);
final HttpClientContext localcontext = HttpClientContext.adapt(
context != null ? context : new BasicHttpContext());
RequestConfig config = null;
if (request instanceof Configurable) {
config = ((Configurable) request).getConfig();
}
if (config == null) {
final HttpParams params = request.getParams();
if (params instanceof HttpParamsNames) {
if (!((HttpParamsNames) params).getNames().isEmpty()) {
config = HttpClientParamConfig.getRequestConfig(params, this.defaultConfig);
}
} else {
config = HttpClientParamConfig.getRequestConfig(params, this.defaultConfig);
}
}
if (config != null) {
localcontext.setRequestConfig(config);
}
setupContext(localcontext);
final HttpRoute route = determineRoute(target, wrapper, localcontext);
return this.execChain.execute(route, wrapper, localcontext, execAware);
先使用的是请求request里面的参数,当参数未设置时才使用的是httpclient配置的参数,向上跟踪源码ApacheHttpClient类
@Override
public Response execute(Request request, Request.Options options) throws IOException {
HttpUriRequest httpUriRequest;
try {
httpUriRequest = toHttpUriRequest(request, options);
} catch (URISyntaxException e) {
throw new IOException("URL '" + request.url() + "' couldn't be parsed into a URI", e);
}
HttpResponse httpResponse = client.execute(httpUriRequest);
return toFeignResponse(httpResponse).toBuilder().request(request).build();
}
HttpUriRequest toHttpUriRequest(Request request, Request.Options options) throws
UnsupportedEncodingException, MalformedURLException, URISyntaxException {
RequestBuilder requestBuilder = RequestBuilder.create(request.method());
//per request timeouts
RequestConfig requestConfig = RequestConfig
.custom()
.setConnectTimeout(options.connectTimeoutMillis())
.setSocketTimeout(options.readTimeoutMillis())
.build();
requestBuilder.setConfig(requestConfig);
.......
HttpUriRequest对Request进行包装,将options设置的参数赋值给Request,从而改变请求的参数,options生成有两种:通过配置(FeignClientProperties类)和通过@Bean生成
1.配置
feign:
client:
config:
athena: #服务名,填写default为所有服务
connectTimeout: 3000
readTimeout: 5000
注:athena为服务名,也就是feign接口FeignClient注解配置的name的名称,如果填写default则默认对所有服务有效
@FeignClient(name = "athena", url = "${feign.athena.server}", configuration = UserFeignConfig.class)
2.设置feign超时时间
/**
* feign接口参数
* @return
*/
@Bean
public Request.Options options() {
return new Request.Options(3000, 5000);
}
该配置对所有的服务有效,和方法1中配置default效果一致
3.最终修改方案
/**
* 设置feign连接池
* @return
*/
@Bean("feignHttpClientConnectionManager")
public HttpClientConnectionManager httpClientConnectionManager(){
DefaultApacheHttpClientConnectionManagerFactory connectionManagerFactory =
new DefaultApacheHttpClientConnectionManagerFactory();
final HttpClientConnectionManager connectionManager = connectionManagerFactory
.newConnectionManager(false, 200,
50, 900, TimeUnit.SECONDS, null);
/**
* 关闭失效链接
*/
ScheduledExecutorService scheduledExecutorService =
new ScheduledThreadPoolExecutor(1);
scheduledExecutorService.scheduleWithFixedDelay(()->connectionManager.closeExpiredConnections(), 30,10, TimeUnit.SECONDS);
return connectionManager;
}
/**
* 设置feign接口httpclient配置
* @return
*/
@Bean("feignHttpClient")
public Client httpClient(HttpClientConnectionManager feignHttpClientConnectionManager) {
/**
* 设置参数
* connectTimeout 3000ms
* connectionRequestTimeout 1000ms
* socketTimeout 5000ms
*/
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(3000)
.setConnectionRequestTimeout(1000)
.setRedirectsEnabled(true)
.setSocketTimeout(5000)
.build();
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
DefaultApacheHttpClientFactory httpClientFactory = new DefaultApacheHttpClientFactory(httpClientBuilder);
/**
* 取消重试
*/
CloseableHttpClient httpClient = httpClientFactory.createBuilder().
setConnectionManager(feignHttpClientConnectionManager).
setDefaultRequestConfig(defaultRequestConfig)
.disableAutomaticRetries()
.build();
return new ApacheHttpClient(httpClient);
}
/**
* feign接口参数
* @return
*/
@Bean
public Request.Options options() {
return new Request.Options(3000, 5000);
}
网友评论