restTemplate未設置連接數(shù)導致服務雪崩問題以及解決
背景
昨天發(fā)版遇到個線上問題,由于運維操作放量時隔離機器過多,導致只有大概三分之一的機器承載全部流量,等于單臺機器的流量突增至正常時候的三倍。
前置對外的api服務開始瘋狂報錯:
ConnectionPoolTimeoutException:Timeout warning for connection from pool
問題分析
連接池滿了。
查看下相關(guān)代碼,用了restTemplate去調(diào)用另外一個子系統(tǒng),繼續(xù)查看關(guān)于連接池的信息:
org.apache.http.conn.ConnectionPoolTimeoutException 是 Apache HttpClient 拋出的一種 山異常,表示從連接池獲取連接超時。
這種異常通常是由以下原因?qū)е拢?/p>
1.連接池中沒有可用的連接。當請求到達時,如果連接池中沒有可用的連接,就會嘗試創(chuàng)建新的連接。如果創(chuàng)建連接的速度很慢,或者連接池中的連接已經(jīng)用完了,就會出現(xiàn)ConnectionPoolTimeoutException 異常。
2.連接池中的連接都被占用。如果連接池中的所有連接都正在被占用,而且沒有連接釋放回池中,就會導致連接池超時異常。3.請求超時時間設置過短。如果設置的請求超時時間過短,就可能在等待連接的過程中超時。
迅速去查看連接池大小配置了多少,發(fā)現(xiàn)并沒有進行相關(guān)的配置,那默認的就是2,這樣遠遠不夠應對當前瞬時的大并發(fā)流量的。
問題解決
立刻進行了相關(guān)配置:
@Bean
public RestTemplate buildRestTemplate(){
final ConnectionKeepAliveStrategy myStrategy = (response, context) -> {
return 5 * 1000;//設置一個鏈接的最大存活時間
};
MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
// 總連接數(shù)
pollingConnectionManager.setMaxTotal(1000);
// 同路由的并發(fā)數(shù)
pollingConnectionManager.setDefaultMaxPerRoute(1000);
HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(pollingConnectionManager)
.setKeepAliveStrategy(myStrategy).build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
httpClient);
factory.setConnectTimeout(3000);
factory.setReadTimeout(5000);
return new RestTemplate(factory);
}直接設置成了1000,交給測試壓測,瞬時200的并發(fā)量壓測也沒有出現(xiàn)ConnectionPoolTimeoutException的問題,看來成功解決了。
restTemplate優(yōu)化點補充
另外,restTemplate還有個點能夠優(yōu)化。
1. RestTemplate 介紹
RestTemplate和Spring提供的JdbcTemplate類似,對象一旦構(gòu)建(使用過程中不對其屬性進行修改)就是線程安全的,多線程環(huán)境下可以安全使用。
2. 場景描述
A系統(tǒng)接口需要訪問B系統(tǒng)接口,正常請求時,這部分代碼耗時不是很明顯(約10ms)。后來,進行接口壓力測試,發(fā)現(xiàn)請求耗時長達500ms,導致整個接口的tps很難上去,調(diào)整線程池參數(shù)效果努力無果,后來對請求B系統(tǒng)接口做了內(nèi)存級別的緩存(guava),tps增長了3倍左右(約500)。
3.問題分析
B系統(tǒng)給出的壓測數(shù)據(jù)顯示,單實例接口的tps在1000+,因此初步排除了B系統(tǒng)接口性能差的可能。于是,開始深究系統(tǒng)本身代碼可能存在的問題。
最開始的代碼編寫方式如下:
String url = "xxx.com/api"'; RestTemplate restTemplate = new RestTemplate(); MemeberCardCodeRspDTO result = restTemplate.getForObject(url, MemeberCardCodeRspDTO.class); System.out.println(result != null ? result.getMsg() : "null");
看上去簡單明了,兩行代碼搞定,非常優(yōu)雅。
后來在組長大大的幫助下,大致定位了問題點,覺得該部分代碼可能存在部分性能問題,應該抽取成單實例,即不能每次使用時重新new一個新的對象。
改造后的代碼為:
@Bean
public RestTemplate buildRestTemplate(){
final ConnectionKeepAliveStrategy myStrategy = (response, context) -> {
return 5 * 1000;//設置一個鏈接的最大存活時間
};
MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
// 總連接數(shù)
pollingConnectionManager.setMaxTotal(1000);
// 同路由的并發(fā)數(shù)
pollingConnectionManager.setDefaultMaxPerRoute(1000);
HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(pollingConnectionManager)
.setKeepAliveStrategy(myStrategy).build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
httpClient);
factory.setConnectTimeout(3000);
factory.setReadTimeout(5000);
return new RestTemplate(factory);
}直接依賴spring bean注解將其定義成單實例對象,其他地方直接屬性注入后使用。
4. 性能分析
問題得到解決后,課余時間又對該部分代碼做了一個粗略的定量分析,本機跑的數(shù)據(jù),還是能比較清晰地得出結(jié)論。
private final String url = "xxxx.com/api";
private int loopCount = 400;
private int concurrentThread = 400;
private RestTemplate restTemplate;
@BeforeTest
public void init() {
final ConnectionKeepAliveStrategy myStrategy = (response, context) -> {
return 5 * 1000;//設置一個鏈接的最大存活時間
};
PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
// 總連接數(shù)
pollingConnectionManager.setMaxTotal(1000);
// 同路由的并發(fā)數(shù)
pollingConnectionManager.setDefaultMaxPerRoute(1000);
HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(pollingConnectionManager)
.setKeepAliveStrategy(myStrategy).build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
httpClient);
factory.setConnectTimeout(3000);
factory.setReadTimeout(5000);
restTemplate = new RestTemplate(factory);
}
@Test
public void testHttp1() {
long start = System.currentTimeMillis();
ExecutorService executor = Executors.newFixedThreadPool(concurrentThread);
for (int i =0 ; i< loopCount; i++){
executor.submit(() -> {
MemeberCardCodeRspDTO result = restTemplate.getForObject(url, MemeberCardCodeRspDTO.class);
System.out.println(result != null ? result.getMsg() : "null");
});
}
try {
executor.shutdown();
executor.awaitTermination(30, TimeUnit.MINUTES); // or longer.
} catch (InterruptedException e) {
e.printStackTrace();
}
long time = System.currentTimeMillis() - start;
System.out.printf("Tasks1 took %d ms to run%n", time);
}
@Test
public void testHttp2() {
long start = System.currentTimeMillis();
ExecutorService executor = Executors.newFixedThreadPool(concurrentThread);
for (int i =0 ; i< loopCount; i++){
executor.submit(() -> {
RestTemplate restTemplate = new RestTemplate();
MemeberCardCodeRspDTO result = restTemplate.getForObject(url, MemeberCardCodeRspDTO.class);
ystem.out.println(result != null ? result.getMsg() : "null");
});
}
try {
executor.shutdown();
executor.awaitTermination(30, TimeUnit.MINUTES); // or longer.
} catch (InterruptedException e) {
e.printStackTrace();
}
long time = System.currentTimeMillis() - start;
System.out.printf("Tasks2 took %d ms to run%n", time);
}調(diào)整threads數(shù)量,跑了6組數(shù)據(jù),結(jié)果對比如下:

圖中比較清晰的可以看出,優(yōu)化過后的代碼性能提升比較明顯,且隨著并發(fā)任務數(shù)增加,耗時波動不會太大。
問題總結(jié)
第三方庫提供的各種方便的類,簡化了編碼復雜度,方便了開發(fā)者。使用不恰當時,細微的編碼可能埋藏著大的隱患。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- restTemplate實現(xiàn)跨服務API調(diào)用方式
- Spring?Cloud?Alibaba?Nacos服務治理平臺服務注冊、RestTemplate實現(xiàn)微服務之間訪問負載均衡訪問的問題
- Java服務調(diào)用RestTemplate與HttpClient的使用詳解
- SpringCloud基于RestTemplate微服務項目案例解析
- springcloud中Ribbon和RestTemplate實現(xiàn)服務調(diào)用與負載均衡
- 關(guān)于springboot 中使用httpclient或RestTemplate做MultipartFile文件跨服務傳輸?shù)膯栴}
相關(guān)文章
springboot接口返回數(shù)據(jù)類型全面解析
這篇文章主要介紹了springboot接口返回數(shù)據(jù)類型問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12
Java畢業(yè)設計實戰(zhàn)之仿小米電子產(chǎn)品售賣商城系統(tǒng)的實現(xiàn)
這是一個使用了java+SpringBoot+Vue+MySQL+Redis+ElementUI開發(fā)的仿小米商城系統(tǒng),是一個畢業(yè)設計的實戰(zhàn)練習,具有小米商城該有的所有基礎功能,感興趣的朋友快來看看吧2022-01-01
springboot?filter配置多個時,執(zhí)行順序問題
這篇文章主要介紹了springboot?filter配置多個時,執(zhí)行順序問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12
一文詳解SpringBoot?Redis多數(shù)據(jù)源配置
Spring?Boot默認只允許一種?Redis?連接池配置,且配置受限于?Lettuce?包,不夠靈活,所以本文將為大家介紹如何自定義Redis配置方案實現(xiàn)多數(shù)據(jù)源支持,需要的可以參考下2024-11-11
Java實現(xiàn)統(tǒng)計字符串出現(xiàn)的次數(shù)
這篇文章主要為大家詳細介紹了Java實現(xiàn)統(tǒng)計字符串出現(xiàn)的次數(shù),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10

