Java-http连接池的使用

最近项目中开发有很多外部http调用,但是我方的接口相应有时效性要求,所以就需要针对项目使用到的http调用进行连接池改造,原先没做也是时效性要求不是很严格,但是现在需要了,就需要整体调整,这也是对原先开发不负责的后果吧。

废话不多说,项目中使用到的http调用方式,这个方式有三种,为啥有三种也不多说了,谁让建项初期没做严格要求,导致大家都是使用自己习惯的方式去完成的设计。

目前有三种:httpclient、resttemplate、feign,接下来针对一样一样的记录了。

1、httpclient

pom依赖的引用:

        org.apache.httpcomponentshttpclient4.5.12

代码工具类:

package com.ifeng.datacenter.assist.utils;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** @author : * @Date: 2020/6/1 16:00* @Description: V-*/
@SuppressWarnings("all")
@Slf4j
public class HttpClientUtil {private static CloseableHttpClient httpClient = null;static {PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();// 总连接池数量connectionManager.setMaxTotal(1500);// 可为每个域名设置单独的连接池数量// connectionManager.setMaxPerRoute(new HttpRoute(new HttpHost("127.0.0.1")), 500);connectionManager.setDefaultMaxPerRoute(150);  // 这个必须设置,默认2,也就是单个路由最大并发数是2// setTcpNoDelay 是否立即发送数据,设置为true会关闭Socket缓冲,默认为false// setSoReuseAddress 是否可以在一个进程关闭Socket后,即使它还没有释放端口,其它进程还可以立即重用端口// setSoLinger 关闭Socket时,要么发送完所有数据,要么等待60s后,就关闭连接,此时socket.close()是阻塞的// setSoTimeout 接收数据的等待超时时间,单位ms// setSoKeepAlive 开启监视TCP连接是否有效SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).setSoReuseAddress(true).setSoLinger(60).setSoTimeout(500).setSoKeepAlive(true).build();connectionManager.setDefaultSocketConfig(socketConfig);// setConnectTimeout表示设置建立连接的超时时间// setConnectionRequestTimeout表示从连接池中拿连接的等待超时时间// setSocketTimeout表示发出请求后等待对端应答的超时时间RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(10000).setConnectionRequestTimeout(10000).setSocketTimeout(10000).build();// 重试处理器,StandardHttpRequestRetryHandler这个是官方提供的,看了下感觉比较挫,很多错误不能重试,可自己实现HttpRequestRetryHandler接口去做
//        HttpRequestRetryHandler retryHandler = new StandardHttpRequestRetryHandler();// 关闭重试策略HttpRequestRetryHandler requestRetryHandler = new DefaultHttpRequestRetryHandler(0, false);// 自定义请求存活策略ConnectionKeepAliveStrategy connectionKeepAliveStrategy = new ConnectionKeepAliveStrategy() {/*** 返回时间单位是毫秒*/@Overridepublic long getKeepAliveDuration(HttpResponse httpResponse, HttpContext httpContext) {return 60 * 1000;}};httpClient = HttpClients.custom().setConnectionManager(connectionManager).setDefaultRequestConfig(requestConfig).setRetryHandler(requestRetryHandler).setKeepAliveStrategy(connectionKeepAliveStrategy).build();}/*** httpclient get** @param uri       请求地址* @param getParams 请求参数* @return*/public static JSONObject doHttpGet(String uri, Map getParams) {HttpGet httpGet = null;CloseableHttpResponse response = null;try {URIBuilder uriBuilder = new URIBuilder(uri);if (null != getParams && !getParams.isEmpty()) {List list = new ArrayList<>();for (Map.Entry param : getParams.entrySet()) {list.add(new BasicNameValuePair(param.getKey(), param.getValue()));}uriBuilder.setParameters(list);}httpGet = new HttpGet(uriBuilder.build());response = httpClient.execute(httpGet);int statusCode = response.getStatusLine().getStatusCode();if (HttpStatus.SC_OK == statusCode) {HttpEntity entity = response.getEntity();if (null != entity) {String resStr = EntityUtils.toString(entity, "utf-8");return JSON.parseObject(resStr);}}} catch (Exception e) {log.error("CloseableHttpClient-get-请求异常", e);} finally {try {if (null != response)response.close();} catch (IOException e) {log.error("CloseableHttpClient-post-请求异常,释放连接异常", e);}try {if (null != httpGet)httpGet.releaseConnection();} catch (Exception e) {log.error("CloseableHttpClient-post-请求异常,释放连接异常", e);}}return new JSONObject();}/*** httpclient post** @param uri       请求地址* @param getParams map化的请求体对象* @return*/public static JSONObject doHttpPost(String uri, Map getParams) {HttpPost httpPost = null;CloseableHttpResponse response = null;try {httpPost = new HttpPost(uri);if (null != getParams && !getParams.isEmpty()) {List list = new ArrayList<>();for (Map.Entry param : getParams.entrySet()) {list.add(new BasicNameValuePair(param.getKey(), param.getValue()));}HttpEntity httpEntity = new UrlEncodedFormEntity(list, "utf-8");httpPost.setEntity(httpEntity);}response = httpClient.execute(httpPost);int statusCode = response.getStatusLine().getStatusCode();if (HttpStatus.SC_OK == statusCode) {HttpEntity entity = response.getEntity();if (null != entity) {String resStr = EntityUtils.toString(entity, "utf-8");return JSON.parseObject(resStr);}}} catch (Exception e) {log.error("CloseableHttpClient-post-请求异常", e);} finally {try {if (null != response)response.close();} catch (IOException e) {log.error("CloseableHttpClient-post-请求异常,释放连接异常", e);}try {if (null != httpPost)httpPost.releaseConnection();} catch (Exception e) {log.error("CloseableHttpClient-post-请求异常,释放连接异常", e);}}return new JSONObject();}/*** httpclient post** @param uri       请求地址* @param reqParams json串* @return*/public static JSONObject doHttpPost(String uri, String reqParams) {HttpPost httpPost = null;CloseableHttpResponse response = null;try {httpPost = new HttpPost(uri);httpPost.addHeader("Content-Type", "application/json;charset=utf-8");if (StringUtils.isNotBlank(reqParams)) {StringEntity postingString = new StringEntity(reqParams, "utf-8");httpPost.setEntity(postingString);}response = httpClient.execute(httpPost);int statusCode = response.getStatusLine().getStatusCode();if (HttpStatus.SC_OK == statusCode) {HttpEntity entity = response.getEntity();if (null != entity) {String resStr = EntityUtils.toString(entity, "utf-8");return JSON.parseObject(resStr);}}} catch (Exception e) {log.error("CloseableHttpClient-post-请求异常", e);} finally {try {if (null != response)response.close();} catch (IOException e) {log.error("CloseableHttpClient-post-请求异常,释放连接异常", e);}try {if (null != httpPost)httpPost.releaseConnection();} catch (Exception e) {log.error("CloseableHttpClient-post-请求异常,释放连接异常", e);}}return new JSONObject();}
}

效果是很显著的,由原先的几百ms到几ms的优化效果。这个效率高实际上就是三次握手四次挥手拜拜的优化,有兴趣的可以找相关资料仔细了解下流程就会明白了。

2、RestTemplate

这个是springboot的web包自带的,所以需要引入web包

        org.springframework.bootspring-boot-starter-web

代码样列:

package com.zh.boot.config;import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;import java.nio.charset.StandardCharsets;
import java.util.List;/*** @author : Lu Ma Ren* @Date: 2020/6/1 16:00* @Description: V-自定义restTemplate模板*/
@SuppressWarnings("all")
@Configuration
public class RestTemplateConfig {@Beanpublic RestTemplate initRestTemplate() {RestTemplate restTemplate = new RestTemplate(httpRequestFactory());// 设置编码格式为UTF-8List> converterList = restTemplate.getMessageConverters();HttpMessageConverter converterTarget = null;for (HttpMessageConverter item : converterList) {if (item.getClass() == StringHttpMessageConverter.class) {converterTarget = item;break;}}if (converterTarget != null) {converterList.remove(converterTarget);}HttpMessageConverter converter = new StringHttpMessageConverter(StandardCharsets.UTF_8);converterList.add(1, converter);return restTemplate;}public ClientHttpRequestFactory httpRequestFactory() {return new HttpComponentsClientHttpRequestFactory(httpClient());}public HttpClient httpClient() {PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();//设置整个连接池最大连接数 根据自己的场景决定connectionManager.setMaxTotal(200);// 可为每个域名设置单独的连接池数量,特殊配置,针对域名或者指定ip配置池大小
//        connectionManager.setMaxPerRoute(new HttpRoute(new HttpHost("10.66.224.52")), 50);//路由是对maxTotal的细分connectionManager.setDefaultMaxPerRoute(100);//SocketTimeout:服务器返回数据(response)的时间,超过该时间抛出read timeout//连接上服务器(握手成功)的时间,超出该时间抛出connect timeout//从连接池中获取连接的超时时间,超过该时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from poolRequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(3000).setConnectTimeout(1000).setConnectionRequestTimeout(1000).build();// 重试处理器,StandardHttpRequestRetryHandler这个是官方提供的,看了下感觉比较挫,很多错误不能重试,可自己实现HttpRequestRetryHandler接口去做HttpRequestRetryHandler retryHandler = new StandardHttpRequestRetryHandler();return HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).setConnectionManager(connectionManager).setRetryHandler(retryHandler).build();}
}

这个也是采用了http连接池去做的,有些码子也是参考了网上一些大佬的资料,就不做一一说明了,看那些资料没留存记录,如果有冒犯了,见谅了,有找到地址了会重新修正文章。

3、feign

这个是cloud组件中的,但是未了避免cloud版本的考虑,直接用的带版本号的pom地址

org.springframework.cloudspring-cloud-starter-openfeign2.2.2.RELEASEorg.springframework.cloudspring-cloud-starter-netflix-hystrix2.2.2.RELEASEio.github.openfeignfeign-httpclient11.0

这块的配置只需要配置文件添加

feign:httpclient:enabled: true

这个的配置有点迷糊,因为没有前两个那么明确的去设置连接池的参数,都是默认的,所以这块就不做评价和说明了,等后续搞清楚了在更新了。

在项目中,由于很多时候配置的连接池或者静态代码的使用,导致项目启动后第一次访问很慢,而在有时效性的接口中,有一定的错误率的要求,所以就需要针对接口进行预加载的处理,springboot也提供这种方便,下边就简要说明下预加载的使用。

在springboot中,只需要实现接口ApplicationRunner,实现run方法即可。

package com.zh.boot.controller.preload;import com.zh.boot.controller.goods.GoodsController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;/*** @author : Lu Ma Ren* @Date: 2020/6/1 16:00* @Description: V-*/
@SuppressWarnings("all")
@Slf4j
@Component
@Order(99)
public class GoodsControllerPre implements ApplicationRunner {@Autowiredprivate GoodsController goodsController;@Overridepublic void run(ApplicationArguments args) throws Exception {goodsController.getGoodsId(100);}
}

该类会在boot项目启动完成后就进行调用,在这说下几点坑,有时候接口中需要使用到加载到内存的数据,所以这个加载顺序以及延时时间都是需要处理考虑到的,不然会在预加载的时候报错,尤其是空指针等这种常见错误。

以上代码都是参考用例,针对自己实际使用需要考虑下具体参数的配置。采用连接池这个思想其实可以使用到很多场景,最大的好处就是重复利用和避免重复创建销毁,就拿feign的连接池配置,在不配置的时候,微服务之间的调用会很慢,但是做了连接池配置,提升的会很明显的。

一点一点积累,一点一点进步


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部