一蟆沫、為什么需要優(yōu)化澳迫?
微服務(wù)之間的RPC調(diào)用使用的OpenFeign組件,并且完全使用的默認設(shè)置澈段,**默認的設(shè)置**包括:
1.1 HTTP客戶端
默認使用的HttpURLConnection悠菜,這是java自帶的發(fā)送http請求的API,優(yōu)點就是java自帶的败富,調(diào)用的時候方便悔醋,缺點就是性能和安全方面
缺點
HttpURLConnection每次請求都會打開一個新的TCP連接,不復(fù)用TCP連接兽叮,這會導(dǎo)致在高并發(fā)場景下HTTP請求和響應(yīng)的速度變慢
比較繁瑣芬骄,需要手動進行請求的創(chuàng)建猾愿、連接、讀取和關(guān)閉等操作账阻;
線程安全性不如其他并發(fā)包蒂秘,需要在多線程環(huán)境中進行適當(dāng)?shù)耐剑?/p>
對響應(yīng)數(shù)據(jù)的讀取需要手動進行,需要調(diào)用IO流API進行讀取淘太。
現(xiàn)狀
目前使用的默認的HttpURLConnection
1.2 重試策略
默認的重試策略
@Configuration
public class FeignClientsConfiguration {
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
}
默認是永不重試姻僧,請求五秒超時后就拋異常結(jié)束
現(xiàn)狀
目前重試策略由第三方組件Guava手動實現(xiàn)重試,需要重試的接口自己實現(xiàn)重試機制琴儿。
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的請求響應(yīng)日志
現(xiàn)狀
目前是默認的不打印feign的請求響應(yīng)日志
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消息轉(zhuǎn)換嘁捷,其中SpringEncoder用于將Java對象轉(zhuǎn)換為請求的HTTP消息體造成,SpringDecoder將響應(yīng)的HTTP消息體轉(zhuǎn)換為相應(yīng)的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
}
現(xiàn)狀
目前是默認配置
二、目前可以優(yōu)化那些方面
目前我們主要想提升的是feign接口的性能雄嚣,因為平時總會有一些feign接口超時晒屎,但是具體去排查,并不是數(shù)據(jù)庫響應(yīng)的問題缓升,所以我們需要對feign本身的請求性能進行優(yōu)化鼓鲁。
重試策略我們可以不做改動,需要重試的地方就自己實現(xiàn)港谊。
feign請求響應(yīng)日志打印這一塊骇吭,我們也為了性能使用默認不打印(好像也沒有這種需要)歧寺。
編碼解碼策略也可以使用默認燥狰,性能提升不在這。
所以優(yōu)化重點放在了Feign的HTTP客戶端斜筐。
三龙致、Feign-HTTP客戶端替換
3.1 我們該如何配置
不做特殊配置的話,在pom文件里面加上http客戶端顷链,SpringBoot應(yīng)用就可以識別并使用到目代。但是需要做特殊配置的話就不行。
<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
# 最大連接數(shù)榛了,默認:200
max-connections: 200
# 最大路由,默認:50
max-connections-per-route: 50
# 連接超時煞抬,默認:2000/毫秒
connection-timeout: 2000
# 生存時間忽冻,默認:900L
time-to-live: 900
# 響應(yīng)超時的時間單位,默認:TimeUnit.SECONDS
# timeToLiveUnit: SECONDS
### Feign 配置
feign:
httpclient:
# 是否開啟 Http Client
enabled: false
# # 最大連接數(shù)此疹,默認:200
# max-connections: 200
# # 最大路由僧诚,默認:50
# max-connections-per-route: 50
# # 連接超時遮婶,默認:2000/毫秒
# connection-timeout: 2000
# # 生存時間,默認:900L
# time-to-live: 900
# # 響應(yīng)超時的時間單位湖笨,默認:TimeUnit.SECONDS
## timeToLiveUnit: SECONDS
okhttp:
enabled: true
那么在9.5.0該如何配置我們的最大連接數(shù)旗扑、連接超時時間等參數(shù)呢?
肯定只能自己寫配置文件來處理了
@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();
}
}
發(fā)起一個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的實現(xiàn)類
public final class OkHttpClient implements Client {
}
當(dāng)Springboot應(yīng)用啟動的時候臀防,檢測到類路徑中有Feign類,并且沒有com.netflix.loadbalancer.ILoadBalancer類時边败,才會加載這個配置袱衷,通俗講,就是如果用到了Spring-Cloud的Ribbon負載均衡組件笑窜,F(xiàn)eignAutoConfiguration就不會加載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導(dǎo)入了OkHttpFeignLoadBalancedConfiguration配置類,配置類里面加載了OkHttpClient并委托給LoadBalancerFeignClient断傲,供之后Feign的http調(diào)用時使用脱吱。
四、OkHttpClient參數(shù)的制定
@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實例所有連接的連接池箱蝠。
如果設(shè)置的過大?
將 maxIdleConnections
設(shè)置得過大可能會占用過多的系統(tǒng)資源垦垂,導(dǎo)致系統(tǒng)性能下降抡锈。因為連接池中的每個空閑連接都需要占據(jù)一定的內(nèi)存谐腰,如果連接池中的連接數(shù)量過多读串,就會占用過多的內(nèi)存資源惯悠。此外躁锡,連接池中的連接也會占用操作系統(tǒng)資源据过,例如文件句柄孝宗、線程等。
另外找蜜,將 maxIdleConnections
設(shè)置得過大也可能會影響網(wǎng)絡(luò)請求的響應(yīng)速度洗做。因為連接池中的連接數(shù)量越多,每個連接就會得到更少的使用機會撰筷,導(dǎo)致連接空閑時間變長毕籽,從而增加網(wǎng)絡(luò)請求的響應(yīng)時間关筒。
因此杯缺,應(yīng)該根據(jù)應(yīng)用的實際情況合理設(shè)置 maxIdleConnections
的值,以平衡資源利用和網(wǎng)絡(luò)請求響應(yīng)速度廉赔。
如果設(shè)置的過小勿负?
將 maxIdleConnections
設(shè)置得過小可能會導(dǎo)致連接池中的連接不足奴愉,從而影響網(wǎng)絡(luò)請求的響應(yīng)速度锭硼。因為當(dāng)連接數(shù)不足時檀头,新的請求需要等待現(xiàn)有的連接釋放岖沛,才能夠得到響應(yīng)婴削。如果請求量很大唉俗,等待連接釋放的時間就會變長,從而導(dǎo)致網(wǎng)絡(luò)請求的響應(yīng)時間變長颂郎。
此外乓序,將 maxIdleConnections
設(shè)置得過小也可能會導(dǎo)致連接頻繁地被創(chuàng)建和關(guān)閉替劈,從而降低連接的重用率得滤,從而增加了系統(tǒng)負擔(dān)和網(wǎng)絡(luò)請求的響應(yīng)時間懂更。
因此沮协,應(yīng)該根據(jù)應(yīng)用的實際情況合理設(shè)置 maxIdleConnections
的值慷暂,以確保連接池中的連接數(shù)量能夠滿足并發(fā)請求的需求行瑞,同時避免連接數(shù)量過多導(dǎo)致資源浪費血久。
到底設(shè)置多少合適?
確定最優(yōu)值需要考慮以下幾個因素:
- 并發(fā)請求的數(shù)量讹蘑。如果同時有很多請求衔肢,那么連接池中就需要有足夠多的空閑連接角骤,以便快速響應(yīng)請求邦尊。
- 服務(wù)器的響應(yīng)速度蝉揍。如果服務(wù)器響應(yīng)速度很快又沾,那么連接池中的連接就會很快被釋放杖刷,可以減少連接池中的空閑連接數(shù)滑燃。
- 應(yīng)用的網(wǎng)絡(luò)環(huán)境表窘。如果網(wǎng)絡(luò)延遲較高乐严,那么連接池中的連接可能需要等待很長時間才能收到響應(yīng)麦备,因此需要更多的空閑連接凛篙。
一般來說呛梆,可以根據(jù)應(yīng)用的實際情況進行調(diào)整填物≈突牵可以嘗試不同的值击困,觀察連接池中的空閑連接數(shù)和請求的響應(yīng)時間阅茶,選擇一個能夠平衡資源利用和響應(yīng)速度的值作為最終的配置蹦浦。