2020-03-03 从Apache PoolingHttpClientConnectionManager看连接

从Apache PoolingHttpClientConnectionManager看连接

缘由: 微服务,以及多子系统外部服务调用的架构中,经常要借用REST方式访问,如果每次都握手连接访问,效率低下。如果没有dubbo这样的RPC框架,又想提高HTTP访问效率,那么HTTP连接池就是比较合适的方式,这样的实现基于keep-alive机制,虽然这样的长连接没有心跳的支持,并且受限于服务提供方的keep-alive时长,但对于连续频繁的服务访问,对比于每次都建立tcp连接,这样的连接复用方式还是相对高效的。

关闭TCP连接需要双方互相挥手完成, 经常出现的CLOSE_WAIT,FIN_WAIT,TIME_WAIT状态就是挥手未结束的中间状态。

PoolingHttpClientConnectionManager管理的是客户端的http连接池。 服务端主动关闭连接时,FIN到client, client 本地的TCP协议栈收到FIN并ACK,但是上层应用程序只有在Read呈现-1时,才回知道server不再发送数据而主动关闭了连接, 然后client调用close关闭连接。

连接池

PoolingHttpClientConnectionManager中的连接放回连接池和创建连接方法: releaseConnection connect

释放连接的时机: //CloseableHttpResponse; InputStream in = response.getEntity().getContent();

中的in实际上是 org.apache.http.client.entity.LazyDecompressingInputStream

其包装了org.apache.http.conn.EofSensorInputStream 其中有watcher org.apache.http.conn.EofSensorWatcher

//class EofSensorInputStream

@Override

    public void close() throws IOException {

        // tolerate multiple calls to close()

        selfClosed = true;

        checkClose();

    }

protected void checkClose() throws IOException {

if (wrappedStream != null) {

    try {

        boolean scws = true; // should close wrapped stream?

        if (eofWatcher != null) {

            scws = eofWatcher.streamClosed(wrappedStream);  //call releaseConnection finally

        }

        if (scws) {

            wrappedStream.close();

        }

    } finally {

        wrappedStream = null;

    }

}

}

附,基于httpClient4.5.2,httpCore4.4.4的HttpClient工具类

public  class HttpClientUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientUtil.class);

private static CloseableHttpClient httpclient = createHttpClientDefault();

private static final String DEFAULT_CHARSET_UTF8 = "UTF-8";

private static final String DEFAULT_CONTENT_TYPE_JSON = "application/json";

private static final int MAX_TIMEOUT = 10000;

private static final int MAX_RETRY_TIMES = 10;

private static final int MAX_THREAD_TOTAL = 50;

/**

* 发送http post请求

* @param action

* @param bodyParam

* @return

* @throws Exception

*/

public static  String post(String action, Object bodyParam) throws Exception{

return post(action, null, bodyParam, null, null);

}

/**

* 发送http post请求

* @param action

* @param bodyParam

* @return

* @throws Exception

*/

public static String post(String action, Map<String, String> headerParam, Object bodyParam) throws Exception{

return post(action, headerParam, bodyParam, null, null);

}

/**

* 发送http post请求

*

* @param action

* @return

* @throws Exception

* @throws UnsupportedEncodingException

*/

public static String post(String action, Map<String, String> headerParam, Object bodyParam, String contentType, String charSet) throws Exception{

String content_type = contentType;

if (content_type == null || "".equals(content_type)) content_type = DEFAULT_CONTENT_TYPE_JSON;

String char_set = charSet;

if (char_set == null || "".equals(char_set)) char_set = DEFAULT_CHARSET_UTF8;

String url = action;

LOGGER.info("Post请求地址:" + url);

HttpPost httpPost = new HttpPost(url);

//header参数

if (headerParam != null && headerParam.size() >0){

LOGGER.info("Post请求Header:" + JSON.toJSONString(headerParam));

for (String key : headerParam.keySet()){

httpPost.addHeader(key, headerParam.get(key));

}

}

//entity数据

if (bodyParam != null ) {

LOGGER.info("Post请求Body:" + JSON.toJSONString(bodyParam));

StringEntity entity = new StringEntity(JSON.toJSONString(bodyParam),char_set);

entity.setContentEncoding(char_set);

entity.setContentType(content_type);

httpPost.setEntity(entity);

}

String resultStr = "";

CloseableHttpResponse response = null;

try {

response = httpclient.execute(httpPost);

processCertainStatus(response.getStatusLine().getStatusCode());

resultStr = EntityUtils.toString(response.getEntity(), "utf-8");

httpPost.reset();

} catch (IOException e) {

LOGGER.error("execute http get connection", e);

} finally {

try {

if(response != null)

response.close();

} catch (IOException e) {

LOGGER.error("close http get connection", e);

}

}

LOGGER.info("Post请求返回:" + resultStr);

return resultStr;

}

/**

* 发送http get请求

* @param action

* @return

* @throws Exception

*/

public static String get(String action) throws Exception{

String url =  action;

LOGGER.info("Get请求地址:" + url);

HttpGet httpGet = new HttpGet(url);

httpGet.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");

CloseableHttpResponse response = null;

String resultStr = "";

try {

response = httpclient.execute(httpGet);

processCertainStatus(response.getStatusLine().getStatusCode());

resultStr = EntityUtils.toString(response.getEntity(),"utf-8");

httpGet.reset();

} catch (IOException e) {

LOGGER.error("execute http get connection", e);

} finally {

try {

if(response != null)

response.close();

} catch (IOException e) {

LOGGER.error("close http get connection", e);

}

}

LOGGER.info("Get请求返回:" + resultStr);

return resultStr;

}

private static  void processCertainStatus(int statusCode){

if(statusCode == 401){

throw new TokenInvalidException("401 token invalid!");

}

}

/**

* 发送http get请求

* @param action

* @return

* @throws Exception

*/

public static String get(String action, Map<String,String> params) throws Exception{

URIBuilder uriBuilder = new URIBuilder();

uriBuilder.setPath(action);

if (params != null){

for (String key: params.keySet()){

uriBuilder.setParameter(key, params.get(key));

}

}

return get(uriBuilder.build().toString());

}

/**

* 创建httpclient

* @return

*/

private static synchronized CloseableHttpClient createHttpClientDefault() {

CloseableHttpClient httpclient = null;

try {

SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null,

new TrustStrategy() {

public boolean isTrusted(

java.security.cert.X509Certificate[] chain,

String authType)

throws java.security.cert.CertificateException {

return true;

}

}).build();

  SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());

  ConnectionSocketFactory psf = PlainConnectionSocketFactory.getSocketFactory(); 


  Registry<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create()

                .register("https", sslsf)

                .register("http", psf)

                .build();

RequestConfig config = RequestConfig.custom()

  .setSocketTimeout(MAX_TIMEOUT)

  .setConnectTimeout(MAX_TIMEOUT)

  .setConnectionRequestTimeout(MAX_TIMEOUT)

  .build();

//超时重试处理

HttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler(MAX_RETRY_TIMES, true);

//连接管理池

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registryBuilder);

cm.setValidateAfterInactivity(60000);

cm.setMaxTotal(MAX_THREAD_TOTAL);

cm.setDefaultMaxPerRoute(MAX_THREAD_TOTAL);

httpclient = HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(config).setRetryHandler(retryHandler).build();

} catch (KeyManagementException e) {

LOGGER.error("KeyManagementException", e);

} catch (NoSuchAlgorithmException e) {

LOGGER.error("NoSuchAlgorithmException", e);

} catch (KeyStoreException e) {

LOGGER.error("KeyStoreException", e);

}

return httpclient;

}

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容