http 连接池 java_HttpClient高并发下性能优化-http连接池

首先,明确两点:

1.http连接池不是万能的,过多的长连接会占用服务器资源,导致其他服务受阻

2.http连接池只适用于请求是经常访问同一主机(或同一个接口)的情况下

3.并发数不高的情况下资源利用率低下

那么,当你的业务符合上面3点,那么你可以考虑使用http连接池来提高服务器性能

使用http连接池的优点:

1.复用http连接,省去了tcp的3次握手和4次挥手的时间,极大降低请求响应的时间

2.自动管理tcp连接,不用人为地释放/创建连接

使用http连接池的大致流程 :

1.创建PoolingHttpClientConnectionManager实例

2.给manager设置参数

3.给manager设置重试策略

4.给manager设置连接管理策略

5.开启监控线程,及时关闭被服务器单向断开的连接

6.构建HttpClient实例

7.创建HttpPost/HttpGet实例,并设置参数

8.获取响应,做适当的处理

9.将用完的连接放回连接池

public class HttpConnectionPoolUtil {

private static Logger logger = LoggerFactory.getLogger(HttpConnectionPoolUtil.class);

private static final int CONNECT_TIMEOUT = Config.getHttpConnectTimeout();// 设置连接建立的超时时间为10s

private static final int SOCKET_TIMEOUT = Config.getHttpSocketTimeout();

private static final int MAX_CONN = Config.getHttpMaxPoolSize(); // 最大连接数

private static final int Max_PRE_ROUTE = Config.getHttpMaxPoolSize();

private static final int MAX_ROUTE = Config.getHttpMaxPoolSize();

private static CloseableHttpClient httpClient; // 发送请求的客户端单例

private static PoolingHttpClientConnectionManager manager; //连接池管理类

private static ScheduledExecutorService monitorExecutor;

private final static Object syncLock = new Object(); // 相当于线程锁,用于线程安全

/**

* 对http请求进行基本设置

* @param httpRequestBase http请求

*/

private static void setRequestConfig(HttpRequestBase httpRequestBase){

RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(CONNECT_TIMEOUT)

.setConnectTimeout(CONNECT_TIMEOUT)

.setSocketTimeout(SOCKET_TIMEOUT).build();

httpRequestBase.setConfig(requestConfig);

}

public static CloseableHttpClient getHttpClient(String url){

String hostName = url.split("/")[2];

System.out.println(hostName);

int port = 80;

if (hostName.contains(":")){

String[] args = hostName.split(":");

hostName = args[0];

port = Integer.parseInt(args[1]);

}

if (httpClient == null){

//多线程下多个线程同时调用getHttpClient容易导致重复创建httpClient对象的问题,所以加上了同步锁

synchronized (syncLock){

if (httpClient == null){

httpClient = createHttpClient(hostName, port);

//开启监控线程,对异常和空闲线程进行关闭

monitorExecutor = Executors.newScheduledThreadPool(1);

monitorExecutor.scheduleAtFixedRate(new TimerTask() {

@Override

public void run() {

//关闭异常连接

manager.closeExpiredConnections();

//关闭5s空闲的连接

manager.closeIdleConnections(Config.getHttpIdelTimeout(), TimeUnit.MILLISECONDS);

logger.info("close expired and idle for over 5s connection");

}

}, Config.getHttpMonitorInterval(), Config.getHttpMonitorInterval(), TimeUnit.MILLISECONDS);

}

}

}

return httpClient;

}

/**

* 根据host和port构建httpclient实例

* @param host 要访问的域名

* @param port 要访问的端口

* @return

*/

public static CloseableHttpClient createHttpClient(String host, int port){

ConnectionSocketFactory plainSocketFactory = PlainConnectionSocketFactory.getSocketFactory();

LayeredConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactory.getSocketFactory();

Registry registry = RegistryBuilder. create().register("http", plainSocketFactory)

.register("https", sslSocketFactory).build();

manager = new PoolingHttpClientConnectionManager(registry);

//设置连接参数

manager.setMaxTotal(MAX_CONN); // 最大连接数

manager.setDefaultMaxPerRoute(Max_PRE_ROUTE); // 路由最大连接数

HttpHost httpHost = new HttpHost(host, port);

manager.setMaxPerRoute(new HttpRoute(httpHost), MAX_ROUTE);

//请求失败时,进行请求重试

HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {

@Override

public boolean retryRequest(IOException e, int i, HttpContext httpContext) {

if (i > 3){

//重试超过3次,放弃请求

logger.error("retry has more than 3 time, give up request");

return false;

}

if (e instanceof NoHttpResponseException){

//服务器没有响应,可能是服务器断开了连接,应该重试

logger.error("receive no response from server, retry");

return true;

}

if (e instanceof SSLHandshakeException){

// SSL握手异常

logger.error("SSL hand shake exception");

return false;

}

if (e instanceof InterruptedIOException){

//超时

logger.error("InterruptedIOException");

return false;

}

if (e instanceof UnknownHostException){

// 服务器不可达

logger.error("server host unknown");

return false;

}

if (e instanceof ConnectTimeoutException){

// 连接超时

logger.error("Connection Time out");

return false;

}

if (e instanceof SSLException){

logger.error("SSLException");

return false;

}

HttpClientContext context = HttpClientContext.adapt(httpContext);

HttpRequest request = context.getRequest();

if (!(request instanceof HttpEntityEnclosingRequest)){

//如果请求不是关闭连接的请求

return true;

}

return false;

}

};

CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).setRetryHandler(handler).build();

return client;

}

/**

* 设置post请求的参数

* @param httpPost

* @param params

*/

private static void setPostParams(HttpPost httpPost, Map params){

List nvps = new ArrayList();

Set keys = params.keySet();

for (String key: keys){

nvps.add(new BasicNameValuePair(key, params.get(key)));

}

try {

httpPost.setEntity(new UrlEncodedFormEntity(nvps, "utf-8"));

} catch (UnsupportedEncodingException e) {

e.printStackTrace();

}

}

public static JsonObject post(String url, Map params){

HttpPost httpPost = new HttpPost(url);

setRequestConfig(httpPost);

setPostParams(httpPost, params);

CloseableHttpResponse response = null;

InputStream in = null;

JsonObject object = null;

try {

response = getHttpClient(url).execute(httpPost, HttpClientContext.create());

HttpEntity entity = response.getEntity();

if (entity != null) {

in = entity.getContent();

String result = IOUtils.toString(in, "utf-8");

Gson gson = new Gson();

object = gson.fromJson(result, JsonObject.class);

}

} catch (IOException e) {

e.printStackTrace();

} finally {

try{

if (in != null) in.close();

if (response != null) response.close();

} catch (IOException e) {

e.printStackTrace();

}

}

return object;

}

/**

* 关闭连接池

*/

public static void closeConnectionPool(){

try {

httpClient.close();

manager.close();

monitorExecutor.shutdown();

} catch (IOException e) {

e.printStackTrace();

}

}

}

关键的地方有以下几点:

1.httpclient实例必须是单例,且该实例必须使用HttpClients.custom().setConnectionManager()来绑定一个PollingHttpClientConnectionManager,这样该client每次发送请求都会通过manager来获取连接,如果连接池中没有可用连接的话,则该会阻塞线程,直到有可用的连接

2.httpclients4.5.x版本直接调用ClosableHttpResponse.close()就能直接把连接放回连接池,而不是关闭连接,以前的版本貌似要调用其他方法才能把连接放回连接池

3.由于服务器一般不会允许无限期的长连接,所以需要开启监控线程,每隔一段时间就检测一下连接池中连接的情况,及时关闭异常连接和长时间空闲的连接,避免占用服务器资源.


版权声明:本文为weixin_28688385原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。