RestTemplate 連接池:記一次壓測(cè)調(diào)優(yōu)
[ 用大白話講解復(fù)雜的技術(shù) ]
最近工作上有些調(diào)整,進(jìn)入了另一個(gè)項(xiàng)目組,本周在給一個(gè)接口做壓力測(cè)試的過(guò)程中,發(fā)現(xiàn) TPS 波動(dòng)比較大:?jiǎn)闻_(tái)服務(wù)器的 TPS 在 1000 到 200 之間波動(dòng),發(fā)現(xiàn)問(wèn)題后我們做了這幾件事兒逐一排問(wèn)題。
1. 排除其他操作對(duì)壓測(cè)的影響
觀察數(shù)據(jù)的時(shí)間戳,發(fā)現(xiàn)在壓測(cè)過(guò)程中有頻發(fā)的數(shù)據(jù)寫(xiě)入,先暫停跑批操作,再次壓測(cè)波動(dòng)依然存在;
2. 代碼檢查,降低數(shù)據(jù)庫(kù)查詢次數(shù)
做代碼檢查,這個(gè)功能需要查詢六次數(shù)據(jù)庫(kù)、調(diào)用一次接口才能獲得所有數(shù)據(jù),經(jīng)過(guò)優(yōu)化后將查詢數(shù)據(jù)庫(kù)的次數(shù)降低為三次,再次壓測(cè)后 TPS 有所提升,但是波動(dòng)的情況還存在;
3. 分階段壓測(cè),縮小問(wèn)題范圍
因?yàn)樵摻涌诘倪壿嬍潜镜剡壿?+ 遠(yuǎn)程調(diào)用,于是先做了擋板單壓本地邏輯,再單獨(dú)壓遠(yuǎn)程接口,兩次壓測(cè)不存在波動(dòng)的問(wèn)題,所以基本上可以確定是調(diào)用接口的過(guò)程中存在一些問(wèn)題;
4. 排除 GC 問(wèn)題
調(diào)用接口的過(guò)程中創(chuàng)建了太多的 Java 對(duì)象,可能會(huì)引發(fā)頻發(fā)的 GC 操作;但是通過(guò)對(duì) JVM 的觀察排除了這個(gè)問(wèn)題;
5. 初步確認(rèn)問(wèn)題
最后將問(wèn)題的范圍縮小,懷疑是 RestTemplate 的使用問(wèn)題;
RestTemplate 是 Spring 提供的用于訪問(wèn) Http 接口的客戶端框架,能夠大大提高客戶端的編寫(xiě)效率,大部分基于 SSM 或 Spring Boot 的項(xiàng)目會(huì)使用 RestTemplate,翻了一下項(xiàng)目中 RestTemplate Config,只配置了超時(shí)時(shí)間:
public class RestTemplateConfig {public RestTemplate restTemplate(){SimpleClientHttpRequestFactory factory =new SimpleClientHttpRequestFactory();factory.setConnectTimeout(30000);factory.setReadTimeout(10000);RestTemplate restTemplate = new RestTemplate(factory);return restTemplate;}}
RestTemplate 默認(rèn)的?
ClientHttpRequestFactor?為
SimpleClientHttpRequestFactory,其中的 createRequest 方法,每次都會(huì)創(chuàng)建一個(gè)新的連接,建立連接本身就是一個(gè)費(fèi)時(shí)費(fèi)力的操作,如果頻繁地對(duì)一個(gè)接口發(fā)起調(diào)用,每次都創(chuàng)建連接會(huì)造成極大的資源浪費(fèi),而且若連接不能及時(shí)釋放,就會(huì)因?yàn)闊o(wú)法建立新的連接導(dǎo)致后面的請(qǐng)求阻塞。
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);prepareConnection(connection, httpMethod.name());if (this.bufferRequestBody) {return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);}else {return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);}}
看到這種情況處理起來(lái)也算簡(jiǎn)單:弄個(gè)連接池;
創(chuàng)建 RestTemplate連接線程池,就需要使用其他的 HTTP 庫(kù)(默認(rèn)使用的是 JDK 自己的),比如 HttpComponents、Netty、OkHttp,在這里我選擇的是 HttpComponents 。
1. 引入依賴:
<dependency><groupId>org.apache.httpcomponentsgroupId><artifactId>httpcoreartifactId><version>version>dependency><dependency><groupId>org.apache.httpcomponentsgroupId><artifactId>httpclientartifactId><version>version>dependency>
2. 修改 RestTemplateConfig :
public?class?RestTemplateConfig?{public RestTemplate restTemplate() {return new RestTemplate(httpRequestFactory());}public ClientHttpRequestFactory httpRequestFactory() {return new HttpComponentsClientHttpRequestFactory(httpClient());}public HttpClient httpClient() {Registryregistry = RegistryBuilder. create() .register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", SSLConnectionSocketFactory.getSocketFactory()).build();PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);//設(shè)置整個(gè)連接池最大連接數(shù)connectionManager.setMaxTotal(400);//路由是對(duì)maxTotal的細(xì)分connectionManager.setDefaultMaxPerRoute(100);RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(30000) //返回?cái)?shù)據(jù)的超時(shí)時(shí)間????????????????.setConnectTimeout(10000)?//連接上服務(wù)器的超時(shí)時(shí)間.setConnectionRequestTimeout(1000) //從連接池中獲取連接的超時(shí)時(shí)間.build();return HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).setConnectionManager(connectionManager).build();}}
修改完成之后再次壓測(cè),單機(jī) TPS 穩(wěn)定在 1200,打完收工。
期待分享
如果您喜歡本文,請(qǐng)點(diǎn)個(gè)“在看”或分享到朋友圈,這將是對(duì)我最大的鼓勵(lì)。
