当前位置: 首页>后端>正文

OpenFeign(9.5.0)性能优化尝试

一、为什么需要优化?

    微服务之间的RPC调用使用的OpenFeign组件,并且完全使用的默认设置,**默认的设置**包括:

1.1 HTTP客户端

默认使用的HttpURLConnection,这是java自带的发送http请求的API,优点就是java自带的,调用的时候方便,缺点就是性能和安全方面

缺点

  • HttpURLConnection每次请求都会打开一个新的TCP连接,不复用TCP连接,这会导致在高并发场景下HTTP请求和响应的速度变慢

  • 比较繁琐,需要手动进行请求的创建、连接、读取和关闭等操作;

  • 线程安全性不如其他并发包,需要在多线程环境中进行适当的同步;

  • 对响应数据的读取需要手动进行,需要调用IO流API进行读取。

现状

目前使用的默认的HttpURLConnection

1.2 重试策略

默认的重试策略

@Configuration
public class FeignClientsConfiguration {
@Bean
@ConditionalOnMissingBean
    public Retryer feignRetryer() {
        return Retryer.NEVER_RETRY;
    }
}

默认是永不重试,请求五秒超时后就抛异常结束

现状

目前重试策略由第三方组件Guava手动实现重试,需要重试的接口自己实现重试机制。

1.3 日志打印策略

 public enum Level {
    /**
     * No logging.
     */
    NONE,
    /**
     * Log only the request method and URL and the response status code and execution time.
     */
    BASIC,
    /**
     * Log the basic information along with request and response headers.
     */
    HEADERS,
    /**
     * Log the headers, body, and metadata for both requests and responses.
     */
    FULL
  }

默认是NONE,不打印feign的请求响应日志

现状

目前是默认的不打印feign的请求响应日志

1.4 编码解码策略

@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
   return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
}

@Bean
@ConditionalOnMissingBean
public Encoder feignEncoder() {
   return new SpringEncoder(this.messageConverters);
}

默认使用是是SpringEncoder和SpringDecoder(它们共同作用于HTTP消息转换,其中SpringEncoder用于将Java对象转换为请求的HTTP消息体,SpringDecoder将响应的HTTP消息体转换为相应的Java对象)

以下是一个HTTP消息体的例子:

POST /api/books HTTP/1.1
Host: example.com
Content-Type: application/json

{
    "title": "The Hitchhiker's Guide to the Galaxy",
    "author": "Douglas Adams",
    "year": 1979,
    "publisher": "Pan Books",
    "isbn": "978-0330508537",
    "language": "English",
    "format": "paperback",
    "pages": 224
}

现状

目前是默认配置

二、目前可以优化那些方面

目前我们主要想提升的是feign接口的性能,因为平时总会有一些feign接口超时,但是具体去排查,并不是数据库响应的问题,所以我们需要对feign本身的请求性能进行优化。

重试策略我们可以不做改动,需要重试的地方就自己实现。

feign请求响应日志打印这一块,我们也为了性能使用默认不打印(好像也没有这种需要)。

编码解码策略也可以使用默认,性能提升不在这。

所以优化重点放在了Feign的HTTP客户端

三、Feign-HTTP客户端替换

3.1 我们该如何配置

不做特殊配置的话,在pom文件里面加上http客户端,SpringBoot应用就可以识别并使用到。但是需要做特殊配置的话就不行。

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

9.5.0版本的OpenFeign是不支持在yaml文件或者properties文件中配置的。在高版本的OpenFeign可以直接在ymal或者properties配置

### Feign 配置
feign:
  httpclient:
    # 开启 Http Client
    enabled: true
    # 最大连接数,默认:200
    max-connections: 200
    # 最大路由,默认:50
    max-connections-per-route: 50
    # 连接超时,默认:2000/毫秒
    connection-timeout: 2000
    # 生存时间,默认:900L
    time-to-live: 900
    # 响应超时的时间单位,默认:TimeUnit.SECONDS
#    timeToLiveUnit: SECONDS

### Feign 配置
feign:
  httpclient:
    # 是否开启 Http Client
    enabled: false
#    # 最大连接数,默认:200
#    max-connections: 200
#    # 最大路由,默认:50
#    max-connections-per-route: 50
#    # 连接超时,默认:2000/毫秒
#    connection-timeout: 2000
#    # 生存时间,默认:900L
#    time-to-live: 900
#    # 响应超时的时间单位,默认:TimeUnit.SECONDS
##    timeToLiveUnit: SECONDS
  okhttp:
    enabled: true


那么在9.5.0该如何配置我们的最大连接数、连接超时时间等参数呢?

肯定只能自己写配置文件来处理了

@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignConfig {
  @Bean
  public OkHttpClient okHttpClient() {
    OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
    clientBuilder.readTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .connectTimeout(5, TimeUnit.SECONDS)
        .retryOnConnectionFailure(true)
        .connectionPool(new ConnectionPool(255, 5, TimeUnit.MINUTES));
        //.addInterceptor(okHttpInterceptor);
    return clientBuilder.build();
  }
}

发起一个Http请求,可以看到我们的配置是生效的

OpenFeign(9.5.0)性能优化尝试,第1张

3.2 Feign(9.5.0)自动配置源码

@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignAutoConfiguration {
    @Configuration
    @ConditionalOnClass(OkHttpClient.class)
    @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
    @ConditionalOnProperty(value = "feign.okhttp.enabled", matchIfMissing = true)
    protected static class OkHttpFeignConfiguration {

        @Autowired(required = false)
        private okhttp3.OkHttpClient okHttpClient;

        @Bean
        @ConditionalOnMissingBean(Client.class)
        public Client feignClient() {
            if (this.okHttpClient != null) {
                return new OkHttpClient(this.okHttpClient);
            }
            return new OkHttpClient();
        }
    }
}
//OkHttpClient是Client的实现类
public final class OkHttpClient implements Client {
  
}

当Springboot应用启动的时候,检测到类路径中Feign类,并且没有com.netflix.loadbalancer.ILoadBalancer类时,才会加载这个配置,通俗讲,就是如果用到了Spring-Cloud的Ribbon负载均衡组件,FeignAutoConfiguration就不会加载OkHttpFeignConfiguration。

那什么时候初始化Feign的Client呢?

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
//Order is important here, last should be the default, first should be optional
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
      OkHttpFeignLoadBalancedConfiguration.class,
      DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
  
}
@Configuration
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty(value = "feign.okhttp.enabled", matchIfMissing = true)
class OkHttpFeignLoadBalancedConfiguration {

    @Autowired(required = false)
    private okhttp3.OkHttpClient okHttpClient;

    @Bean
    @ConditionalOnMissingBean(Client.class)
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                              SpringClientFactory clientFactory) {
        OkHttpClient delegate;
        if (this.okHttpClient != null) {
            delegate = new OkHttpClient(this.okHttpClient);
        } else {
            delegate = new OkHttpClient();
        }
        return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
    }
}

OK,我们看到在FeignRibbonClientAutoConfiguration类里面,主动使用@Import导入了OkHttpFeignLoadBalancedConfiguration配置类,配置类里面加载了OkHttpClient并委托给LoadBalancerFeignClient,供之后Feign的http调用时使用。

四、OkHttpClient参数的制定

@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignConfig {
  @Bean
  public OkHttpClient okHttpClient() {
    OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
    clientBuilder.readTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .connectTimeout(5, TimeUnit.SECONDS)
        .retryOnConnectionFailure(true)
        .connectionPool(new ConnectionPool(255, 5, TimeUnit.MINUTES));
        //.addInterceptor(okHttpInterceptor);
    return clientBuilder.build();
  }
}

4.1 maxIdleConnections

maxIdleConnections 连接池大小,指单个okhttpclient实例所有连接的连接池。

如果设置的过大?

maxIdleConnections 设置得过大可能会占用过多的系统资源,导致系统性能下降。因为连接池中的每个空闲连接都需要占据一定的内存,如果连接池中的连接数量过多,就会占用过多的内存资源。此外,连接池中的连接也会占用操作系统资源,例如文件句柄、线程等。

另外,将 maxIdleConnections 设置得过大也可能会影响网络请求的响应速度。因为连接池中的连接数量越多,每个连接就会得到更少的使用机会,导致连接空闲时间变长,从而增加网络请求的响应时间。

因此,应该根据应用的实际情况合理设置 maxIdleConnections 的值,以平衡资源利用和网络请求响应速度。

如果设置的过小?

maxIdleConnections 设置得过小可能会导致连接池中的连接不足,从而影响网络请求的响应速度。因为当连接数不足时,新的请求需要等待现有的连接释放,才能够得到响应。如果请求量很大,等待连接释放的时间就会变长,从而导致网络请求的响应时间变长。

此外,将 maxIdleConnections 设置得过小也可能会导致连接频繁地被创建和关闭,从而降低连接的重用率,从而增加了系统负担和网络请求的响应时间。

因此,应该根据应用的实际情况合理设置 maxIdleConnections 的值,以确保连接池中的连接数量能够满足并发请求的需求,同时避免连接数量过多导致资源浪费。

到底设置多少合适?

确定最优值需要考虑以下几个因素:

  1. 并发请求的数量。如果同时有很多请求,那么连接池中就需要有足够多的空闲连接,以便快速响应请求。
  2. 服务器的响应速度。如果服务器响应速度很快,那么连接池中的连接就会很快被释放,可以减少连接池中的空闲连接数。
  3. 应用的网络环境。如果网络延迟较高,那么连接池中的连接可能需要等待很长时间才能收到响应,因此需要更多的空闲连接。

一般来说,可以根据应用的实际情况进行调整。可以尝试不同的值,观察连接池中的空闲连接数和请求的响应时间,选择一个能够平衡资源利用和响应速度的值作为最终的配置。


https://www.xamrdz.com/backend/3f91935372.html

相关文章: