一、为什么需要优化?
微服务之间的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请求,可以看到我们的配置是生效的
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
的值,以确保连接池中的连接数量能够满足并发请求的需求,同时避免连接数量过多导致资源浪费。
到底设置多少合适?
确定最优值需要考虑以下几个因素:
- 并发请求的数量。如果同时有很多请求,那么连接池中就需要有足够多的空闲连接,以便快速响应请求。
- 服务器的响应速度。如果服务器响应速度很快,那么连接池中的连接就会很快被释放,可以减少连接池中的空闲连接数。
- 应用的网络环境。如果网络延迟较高,那么连接池中的连接可能需要等待很长时间才能收到响应,因此需要更多的空闲连接。
一般来说,可以根据应用的实际情况进行调整。可以尝试不同的值,观察连接池中的空闲连接数和请求的响应时间,选择一个能够平衡资源利用和响应速度的值作为最终的配置。