網(wǎng)關(guān)使用 Apache HttpClient 連接池出現(xiàn)異常
最近網(wǎng)關(guān)發(fā)版出現(xiàn)大量如下異常,而有如下文章:
org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool兩個(gè)主機(jī)建立網(wǎng)絡(luò)連接是一個(gè)比較復(fù)雜的過程,涉及到多個(gè)數(shù)據(jù)包的交換。建立網(wǎng)絡(luò)連接本身就很耗時(shí)間,而 Http 連接需要三次握手,開銷就更大。但是可以直接使用已經(jīng)建立好的 Http?連接,那么花費(fèi)就比較小。耗時(shí)更短,從而提高訪問的吞吐量。
傳統(tǒng)的 HttpURLConnection 并不支持連接池,如果要實(shí)現(xiàn)連接池機(jī)制,那么需要自己來管理連接對(duì)象。對(duì)于網(wǎng)絡(luò)請(qǐng)求這種底層相對(duì)復(fù)雜的操作,沒有一定經(jīng)驗(yàn)的程序員很難寫好這塊代碼邏輯。
除了 HttpURLConnection,常用的Http Client 要數(shù) Apache 的 HttpClient。一般情況下, HttpClient 已經(jīng)能滿足業(yè)務(wù)需求了;但是在網(wǎng)關(guān)這種高并發(fā)場(chǎng)景下,使用 HttpClient? 進(jìn)行大量的請(qǐng)求網(wǎng)絡(luò),還是需要用連接池才能提高網(wǎng)關(guān)的TPS,不然很容易成為網(wǎng)關(guān)的瓶頸。
Apache 的?HttpClient的早期版本,提供了PoolingClientConnectionManager、DefaultHttpClient 等類來實(shí)現(xiàn) Http?連接池,但這些類在 4.3.x 版本之后大部分已過時(shí)。后續(xù)版本提供了PoolingHttpClientConnectionManager 等類進(jìn)行 Http??連接池的實(shí)現(xiàn)。PoolingHttpClientConnectionManager 是一個(gè) Http 連接池管理器,用來服務(wù)于多線程時(shí)并發(fā)獲取連接的請(qǐng)求。每個(gè)路由(IP)將池化不大于 defaultMaxPerRoute 參數(shù)的連接。
pom.xml?文件引入依賴
<dependency>?????<groupId>org.apache.httpcomponentsgroupId>??????<artifactId>httpclientartifactId>??????<version>4.5.13version>dependency><dependency>???? <groupId>org.apache.httpcomponentsgroupId>????? <artifactId>httpcoreartifactId>???????<version>4.4.15version>dependency><dependency><groupId>org.apache.httpcomponentsgroupId><artifactId>httpclient-cacheartifactId>??????<version>4.5.13version>dependency>
定義連接池主要參數(shù)
public?class?HttpPoolProperties?{private Integer maxTotal;????private?Integer?maxRoute;private Integer defaultMaxPerRoute;private String hostName;private Integer port;private Integer connectTimeout;private Integer connectionRequestTimeout;????private?Integer?socketTimeout;private Integer validateAfterInactivity;????//?TODO 省略get set 方法}
maxTotal:允許跨所有路線的最大連接數(shù)
defaultMaxPerRoute:所有路由默認(rèn)最大連接數(shù)
maxPerRoute:指定某個(gè)路由的最大連接數(shù)
defaultMaxPerRoute?與?maxPerRoute?這兩個(gè)參數(shù)存在優(yōu)先級(jí)關(guān)系。具體可以參看 AbstractConnPool 類的如下方法的引用關(guān)系
private int getMax(T route) {??? Integer?v?=?(Integer)this.maxPerRoute.get(route);return v != null ? v : this.defaultMaxPerRoute;}
connectTimeout:多久等待與遠(yuǎn)程服務(wù)器拋出超時(shí)異常之前建立連接
socketTimeout:多久等待服務(wù)器拋出超時(shí)異常之前,各種消息響應(yīng)
http://docs.oracle.com/javase/1.5.0/docs/api/java/net/SocketOptions.html#SO_TIMEOUTconnectionRequestTimeout:多久試圖拋出異常之前,先從連接池中的連接時(shí)等待(連接池不會(huì)立即返回,如果所有的連接被檢出)
staleConnectionCheckEnabled:可以在潛在的 IOExceptions 成本的性能有所提高被禁用
http://hc.apache.org/httpclient-3.x/performance.html#Stale_connection_check獲取 HttpClient 對(duì)象
public static CloseableHttpClient createHttpClient(HttpPoolProperties httpPoolProperties) {ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();Registryregistry = RegistryBuilder .create().register("http", plainsf) .register("https", sslsf).build();PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);// 將最大連接數(shù)增加cm.setMaxTotal(httpPoolProperties.getMaxTotal());// 將每個(gè)路由基礎(chǔ)的連接增加cm.setDefaultMaxPerRoute(httpPoolProperties.getDefaultMaxPerRoute());if(StringUtils.hasText(httpPoolProperties.getHostName()) && httpPoolProperties.getPort()!=null) {HttpHost httpHost = new HttpHost(httpPoolProperties.getHostName(), httpPoolProperties.getPort());// 將目標(biāo)主機(jī)的最大連接數(shù)增加cm.setMaxPerRoute(new HttpRoute(httpHost), httpPoolProperties.getMaxRoute());}// 請(qǐng)求重試處理HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {// 如果已經(jīng)重試了5次,就放棄if (executionCount >= 5) {return false;}??????????????????//?如果服務(wù)器丟掉了連接,那么就重試if (exception instanceof NoHttpResponseException) {return true;}// 不要重試SSL握手異常if (exception instanceof SSLHandshakeException) {return false;}// 超時(shí)if (exception instanceof InterruptedIOException) {return false;}// 目標(biāo)服務(wù)器不可達(dá)????????????????if?(exception?instanceof?UnknownHostException)?{return false;}// 連接被拒絕if (exception instanceof ConnectTimeoutException) {return false;}// SSL握手異常if (exception instanceof SSLException) {return false;}HttpClientContext clientContext = HttpClientContext.adapt(context);HttpRequest request = clientContext.getRequest();// 如果請(qǐng)求是冪等的,就再次嘗試if (!(request instanceof HttpEntityEnclosingRequest)) {return true;}return false;}};CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).setRetryHandler(httpRequestRetryHandler).build();return httpClient;}
記得點(diǎn)「贊」和「在看」↓
愛你們
