java.lang.IllegalStateException: Connection pool shut down
最近使用HttpClient 4.5 使用 CloseableHttpClient 发起连接后,使用CloseableHttpResponse 接受返回结果,结果就报错了,上网查了下,有位stackoverflow的大兄弟说,只要将:
CloseableHttpClient httpClient = HttpClients.createDefault();
改为:
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connManager).setConnectionManagerShared(true).build();
就可以整正常执行了,于是,用之,果然不报错了,但是为什么呢?以下是大兄弟的原文解释:
I was having a similar error when I came across this thread and this seemed to fix the issue for me. I know this is an old question, but adding thoughts for others for future reference.
I'm not 100% sure as to why this fix works as the documentation around this is pretty awful. It was a lot of trial and error with what I was testing to get to this solution. From what I can
gather though, this fix works because it is then using a shared connection pool in the background, which means that connections remain open for use.
关键是最后一句话:大概意思是,后台使用一个共享连接池,供剩下打开的连接去使用
原文地址:https://stackoverflow.com/questions/41744410/executorservice-performing-rest-requests
感谢下这位大兄弟
apache 官方的建议是,创建连接池,并为每一个接口URL分配一个线程,去执行,还给出了许多高并发访问的编码技巧
原文:https://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html
那么,使用HttpClient 4.5连接池的正确姿势是什么呢?
原作者地址:https://my.oschina.net/xlj44400/blog/711341
HttpClient简介
HttpClient 是 Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient支持的功能如下:
- 支持Http0.9、Http1.0和Http1.1协议。
- 实现了Http全部的方法(GET,POST,PUT,HEAD 等)。
- 支持HTTPS协议。
- 支持代理服务器。
- 提供安全认证方案。
- 提供连接池以便重用连接。
- 连接管理器支持多线程应用。支持设置最大连接数,同时支持设置每个主机的最大连接数,发现并关闭过期的连接。
- 在http1.0和http1.1中利用KeepAlive保持长连接。
以前是commons-httpclient,后面被Apache HttpComponents取代,目前版本4.5.x,我们现在用的就是4.5版本
HttpClient连接池使用
为什么要用Http连接池:
1、降低延迟:如果不采用连接池,每次连接发起Http请求的时候都会重新建立TCP连接(经历3次握手),用完就会关闭连接(4次挥手),如果采用连接池则减少了这部分时间损耗
2、支持更大的并发:如果不采用连接池,每次连接都会打开一个端口,在大并发的情况下系统的端口资源很快就会被用完,导致无法建立新的连接
- 默认http协议:
private static final Charset CHAR_SET = Charset.forName("utf-8");
private static PoolingHttpClientConnectionManager cm;
public void init() {
cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(50);
cm.setDefaultConnectionConfig(ConnectionConfig.custom()
.setCharset(CHAR_SET).build());
SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(30000)
.setSoReuseAddress(true).build();
cm.setDefaultSocketConfig(socketConfig);
// HttpProtocolParams.setContentCharset(httpParams, "UTF-8");
// HttpClientParams.setCookiePolicy(httpParams, "ignoreCookies");
// HttpConnectionParams.setConnectionTimeout(httpParams, 30000);
// HttpConnectionParams.setSoTimeout(httpParams, 30000);
httpClient = HttpClientBuilder.create().setConnectionManager(cm)
.build();
}
public CloseableHttpClient getHttpClient() {
int timeout=2;
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(timeout * 1000) //设置连接超时时间,单位毫秒
//.setConnectionRequestTimeout(timeout * 1000) //设置从connect Manager获取Connection 超时时间,单位毫秒
.setSocketTimeout(timeout * 1000).build(); //请求获取数据的超时时间,单位毫秒
CloseableHttpClient _httpClient = HttpClients.custom()
.setConnectionManager(cm).setDefaultRequestConfig(config)
.build();
if(cm!=null&&cm.getTotalStats()!=null) { //打印连接池的状态
LOGGER.info("now client pool {}",cm.getTotalStats().toString());
}
return _httpClient;
}
public String post(String url, Map<String, String> params) {
HttpPost post = new HttpPost(url);
String resp = null;
try {
if(params != null){
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
for (Map.Entry<String, String> param : params.entrySet()) {
nvps.add(new BasicNameValuePair(param.getKey(), param.getValue()));
}
post.setEntity(new UrlEncodedFormEntity(nvps, CHAR_SET));
}
try {
HttpResponse response = httpClient.execute(post);
InputStream input = response.getEntity().getContent();
resp = IOUtils.toString(input);
} catch (ClientProtocolException e) {
LOGGER.error(e.getMessage(), e);
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
} finally {
if (post != null)
post.releaseConnection();
}
return resp;
}
- https协议:
public class HttpConnectionManager {
PoolingHttpClientConnectionManager cm = null;
public void init() {
LayeredConnectionSocketFactory sslsf = null;
try {
sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
.register("https", sslsf)
.register("http", new PlainConnectionSocketFactory())
.build();
cm =new PoolingHttpClientConnectionManager(socketFactoryRegistry);
cm.setMaxTotal(200);
cm.setDefaultMaxPerRoute(20);
}
public CloseableHttpClient getHttpClient() {
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
/*
//如果不采用连接池就是这种方式获取连接
CloseableHttpClient httpClient = HttpClients.createDefault();
*/
return httpClient;
}
}
- httpClient使用
catch (Exception e) {
logger.error("ufile send error e:",e);
try {
if (resEntity != null && resEntity.getContent() != null) {
resEntity.getContent().close();
}
} catch (IllegalStateException | IOException e1) {
logger.error("ufile send error e1:",e1);
} finally {
if (getMethod!=null) {
getMethod.releaseConnection();
}
/*if (httpClient!=null) { //连接池使用的时候不能关闭连接,否则下次使用会抛异常 java.lang.IllegalStateException: Connection pool shut down
try {
httpClient.close();
} catch (IOException e2) {
logger.error("ufile httpclient close error e2:",e2);
}
}*/
}
}
-
连接池使用注意事项:
1. 连接池中连接都是在发起请求的时候建立,并且都是长连接 2. HttpResponse input.close();作用就是将用完的连接释放,下次请求可以复用,这里特别注意的是,如果不使用in.close();而仅仅使用httpClient.close();结果就是连接会被关闭,并且不能被复用,这样就失去了采用连接池的意义。 3. 连接池释放连接的时候,并不会直接对TCP连接的状态有任何改变,只是维护了两个Set,leased和avaliabled,leased代表被占用的连接集合,avaliabled代表可用的连接的集合,释放连接的时候仅仅是将连接从leased中remove掉了,并把连接放到avaliabled集合中
打印的状态:
INFO c.m.p.u.h.HttpClientUtils[72] - now client pool [leased: 0; pending: 0; available: 0; max: 50]
leased :the number of persistent connections tracked by the connection manager currently being used to execute requests.
available :the number idle persistent connections.
pending : the number of connection requests being blocked awaiting a free connection.
max: the maximum number of allowed persistent connections.
HttpClient 4.5超时设置
4.5版本中,这两个参数的设置都抽象到了RequestConfig中,由相应的Builder构建,具体的例子如下:
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://stackoverflow.com/");
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(5000).setConnectionRequestTimeout(1000)
.setSocketTimeout(5000).build();
httpGet.setConfig(requestConfig);
CloseableHttpResponse response = httpclient.execute(httpGet);
System.out.println("得到的结果:" + response.getStatusLine());//得到请求结果
HttpEntity entity = response.getEntity();//得到请求回来的数据
- setConnectTimeout:设置连接超时时间,单位毫秒。
ConnectTimeoutException
- setConnectionRequestTimeout:设置从connect Manager获取Connection 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。
ConnectionPoolTimeout
- setSocketTimeout:请求获取数据的超时时间,单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。
SocketTimeoutException
- 上面3个时间4.5版本默认是-1,就是不限,如果不设置就会一直等待