Spring Cloud Feign性能优化

概述

默认情况下,spring cloud feign在进行各个服务之间调用时,http组件使用的是jdk的HttpURLConnection,而不是使用连接池。为了优化调用效率,提升性能,项目在生产环境一般都会使用连接池。

版本

springboot 1.5.16.RELESE,springCloud Edgware.RELEASE

源码分析

在spring-cloud-netflix-core/META-INF/spring.factories中可以看到,在springboot自动配置会初始化FeignRibbonClientAutoConfiguration类

查看FeignRibbonClientAutoConfiguration

此类引入3个类:HttpClientFeignLoadBalancedConfiguration、OkHttpFeignLoadBalancedConfiguration、DefaultFeignLoadBalancedConfiguration。

HttpClientFeignLoadBalancedConfiguration

为feign配置Apache httpClient连接池, 当引入ApacheHttpClient类时,会初始化这个配置类。feignClient方法中,根据@ConditionalOnMissingBean注解判断,如果已有HttpClient对象,则使用该对象,如果没有,则使用默认值,最后生成LoadBalancerFeignClient对象。

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

}

OkHttpFeignLoadBalancedConfiguration

为feign配置Okhttp,原理同HttpClientFeignLoadBalancedConfiguration。

@Configuration
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty(value = "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);
}

DefaultFeignLoadBalancedConfiguration

为feign配置HttpURLConnection,feignClient方法,只有以上两个Client没有生产对象时,才在这个方法中使用Client.Default生成LoadBalancerFeignClient。

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

查看Client.Default源码

public static class Default implements Client {

  @Override
  public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection).toBuilder().request(request).build();
}

HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
  final HttpURLConnection connection = (HttpURLConnection) new URL(request.url()).openConnection();
      ...
  }
 ...
}

Default使用HttpURLConnection建立连接且每次请求都建立一个新的连接。

综上,默认情况下,spring cloud没有引入httpclient和okhhtp的jar包,所以默认使用HttpURLConnection。

使用Http连接池

默认情况下,服务间调用使用HttpURLConnection,效率比较低。可以通过连接池提高效率。

配置Apache httpclient连接池

pom.xml中引入feign-httpclient依赖

<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-httpclient</artifactId>
</dependency>

appliction.yml中添加配置

# 配置httpClient线程池
feign:  
 httpclient:
   enabled: true
 okhttp:
   enabled: false

配置OkHttpClient连接池

pom.xml中引入feign-okhttp依赖

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

appliction.yml中添加配置

# 配置httpClient线程池
feign:  
 httpclient:
   enabled: false
 okhttp:
   enabled: true

spring cloud中的FeignHttpClientProperties提供了默认参数配置

@ConfigurationProperties(prefix = "feign.httpclient")
public class FeignHttpClientProperties {
    public static final boolean DEFAULT_DISABLE_SSL_VALIDATION = false;
    public static final int DEFAULT_MAX_CONNECTIONS = 200;
    public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 50;
    public static final long DEFAULT_TIME_TO_LIVE = 900L;
    public static final TimeUnit DEFAULT_TIME_TO_LIVE_UNIT = TimeUnit.SECONDS;
    public static final boolean DEFAULT_FOLLOW_REDIRECTS = true;
    public static final int DEFAULT_CONNECTION_TIMEOUT = 2000;
    public static final int DEFAULT_CONNECTION_TIMER_REPEAT = 3000;
    ...
}

实践中,可以根据生产配置自行调整http相关参数。

# httpclient参数配置
feign:
  httpclient:
    enabled: true
    max-connections: 200 # 默认值
    max-connections-per-route: 50 # 默认值

 # Okhttp参数配置
 feign:
  httpclient:
    enabled: true
    max-connections: 200 # 默认值
    max-connections-per-route: 50 # 默认值

Jmeter测试

测试结果仅供参考

项目代码:https://github.com/zyhfight/springcloud/tree/master/weather

配置:

本地开启redis、jmeter、idea、chrome浏览器等应用。

http参数配置均采用spring cloud提供的默认值。

jmeter模拟1s内1000并发

默认HttpURLConnection

Apache HttpClient

OkHttpClient

jmeter模拟1s内1w并发,执行了3次

默认HttpURLConnection

Apache HttpClient

OkHttpClient

测试结果分析

如图所示,OKhttp性能明显优于其他两种。1s内1w并发,使用Apache httpclient效率好像还没有默认的HttpURLConnection效率高。且在压测过程中,发现使用连接池请求卡顿现象很容易出现,apache httpclient甚至还出现请求卡死情况。高并发下,有些线程迟迟得不到执行权,超时时间又设置太久,就一直挂在那里。

如果使用了连接池,但是高并发下连接不够用,造成大量等待,且这种等待存在滚雪球效应。

测试中,http参数配置采用了默认值,feign其他配置如下:

feign:
  client:
    config:
      feignName:
        connnectTimeout: 50000 # 连接超时,默认10s
        readTimeout: 50000 # 读取超时, 默认60s
  hystrix:
    enabled: true
    command:
      default:
        circuitBreaker:
          requestVolumeThreshold: 1000 # 熔断器失败的个数,默认20个,进入熔断器的请求达到1000时服务降级(之后的请求直接进入熔断器)
        fallback:
          isolation:
            semaphore:
              maxConcurrentRequests: 10000 # 执行回退逻辑的最大并发线程数,默认10个
        execution:
          timeout:
            enabled: true
          isolation:
            thread:
              timeoutInMilliseconds: 60000 # 请求处理的超时时间
    threadpool:
      default:
        coreSize: 10 # 核心线程池数量,默认10个

上文参数值,都是随意设置的,实践中要了解各参数含义,设定合理的参数值!

异常

com.netflix.hystrix.exception.HystrixRuntimeException

异常信息:

com.netflix.hystrix.exception.HystrixRuntimeException: DataClient#listCity() fallback execution rejected.

异常原因:

执行错误,本应去执行fallback方法,但是却被rejected了。
这种情况下,一般都是熔断机制被触发,所有请求都进入fallback。

fallback默认是有个并发最大处理的限制,fallback.isolation.semaphore.maxConcurrentRequests,默认是10。

fallback方法即使很简单,处理很快,可是如果QPS很高,还是很容易达到限制阈值,hystrix为了自我保护,后面的请求会拒绝。

解决方案:

fallback方法处理要尽可能简单,不要有耗时的操作,如果一个http接口作为另一个http接口的降级处理,此时也必须要考虑这个http是不是也会失败。

可以适当增大fallback.isolation.semaphore.maxConcurrentRequests。

对抛出的异常进行处理,返回用户友好提示。

github issue

org.springframework.beans.factory.BeanCreationNotAllowedException

异常信息:

org.springframework.beans.factory.BeanCreationNotAllowedException: Error creating bean with name ‘eurekaAutoServiceRegistration’: Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)

异常原因:

IDEA编辑器中直接stop all就会产生此问题。

spring cloud中的bug,由于EventListener在应用shutdown时,不能很好的监听处理。

解决方案:

自行在代码中增加判断逻辑(测试可行)
FeignBeanFactoryPostProcessor

spring cloud的Greenwich.M1版本修复了这个问题,使用ApplicationListener替换了EventListener。(未亲测)

github issue

Greenwich.M1 解决方案

参考

https://blog.csdn.net/hry2015/article/details/79904815

http://www.cnblogs.com/AnXinliang/p/10019550.html