feign取消http请求重试

2020-05-19  本文已影响0人  定金喜

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);
    }
上一篇 下一篇

猜你喜欢

热点阅读