1. HttpClient 简介
HttpClient 是Apache的一个子项目,是可以提供支持HTTP协议的Java客户端编程工具包。在实际项目的使用过程中,经常都是多线程访问,因此可能存在多个线程都需要调用HttpClient对象的情况,这类似于数据库连接,所以我们需要对连接进行池化管理,以便于提高性能。
HttpClient从4.2开始抛弃了先前的SingleClientConnManager
和ThreadSafeConnManger
,取而代之的是BasicClientConnectionManager
和PoolingClientConnectionManager
,本文使用的是HttpClient 4.5 版本。
2.为什么要用Http连接池
1、降低延迟:如果不采用连接池,每次连接发起Http请求的时候都会重新建立TCP连接(经历3次握手),用完就会关闭连接(4次挥手),如果采用连接池则减少了这部分时间损耗,别小看这几次握手,本人经过测试发现,基本上3倍的时间延迟
2、支持更大的并发:如果不采用连接池,每次连接都会打开一个端口,在大并发的情况下系统的端口资源很快就会被用完,导致无法建立新的连接
3. 编码
1、HttpConnectionManager.java连接池管理类,支持https协议
PoolingHttpClientConnectionManager实现了HttpClientConnectionManager接口,顾名思义,它就是用来对连接进行池化管理的,首先创建一个对象,并在类加载时初始化它:
@Component
public class HttpConnectionManager {
PoolingHttpClientConnectionManager cm = null;
@PostConstruct
public void init() {
LayeredConnectionSocketFactory sslsf = null;
try {
sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
//配置时同时支持http、https
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
.register("https", sslsf)
.register("http", new PlainConnectionSocketFactory())
.build();
//初始化连接管理器
cm =new PoolingHttpClientConnectionManager(socketFactoryRegistry);
//最大连接数200
cm.setMaxTotal(200);
//设置最大路由器
cm.setDefaultMaxPerRoute(20);
}
//创建一个安全的httpclient对象
public CloseableHttpClient getHttpClient() {
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
/*CloseableHttpClient httpClient = HttpClients.createDefault();//如果不采用连接池就是这种方式获取连接*/
return httpClient;
}
}
2、连接池消费类:HaoMaiClient.java
@Component
public class HaoMaiClient {
@Autowired
HttpConnectionManager connManager;
public <T> T get(String path,Class<T> clazz){
CloseableHttpClient httpClient = connManager.getHttpClient();
//实例化GET请求
HttpGet httpget = new HttpGet(path);
//JSON数据格式
String json=null;
CloseableHttpResponse response=null;
try {
response = httpClient.execute(httpget);
InputStream in=response.getEntity().getContent();
json=IOUtils.toString(in);
in.close();
} catch (UnsupportedOperationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(response!=null){
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return JSON.parseObject(json, clazz);
}
}
4.原理及注意事项
连接池中连接都是在发起请求的时候建立,并且都是长连接
HaoMaiClient.java中的in.close();作用就是将用完的连接释放,下次请求可以复用,这里特别注意的是,如果不使用in.close();而仅仅使用response.close();结果就是连接会被关闭,并且不能被复用,这样就失去了采用连接池的意义。
连接池释放连接的时候,并不会直接对TCP连接的状态有任何改变,只是维护了两个Set,leased和avaliabled,leased代表被占用的连接集合,avaliabled代表可用的连接的集合,释放连接的时候仅仅是将连接从leased中remove掉了,并把连接放到avaliabled集合中
5. 一些总结
3.1 每个路由(route)最大连接数
上面我们有一行这样的代码:poolConnManager.setDefaultMaxPerRoute(2);
用于设置最大路由,这里route的概念可以理解为 运行环境机器 到 目标机器 的一条线路。举例来说,我们使用HttpClient的实现来分别请求 www.baidu.com 的资源和 www.bing.com 的资源那么他就会产生两个route。以下是网上对设置这个参数的一些解释:
这里为什么要特别提到route最大连接数这个参数呢,因为这个参数的默认值为2,如果不设置这个参数,值默认情况下对于同一个目标机器的最大并发连接只有2个!这意味着如果你正在执行一个针对某一台目标机器的抓取任务的时候,哪怕你设置连接池的最大连接数为200,但是实际上还是只有2个连接在工作,其他剩余的198个连接都在等待,都是为别的目标机器服务的。
3.2 BasicClientConnectionManager
BasicClientConnectionManager内部只维护一个活动的connection,尽管这个类是线程安全的,但是最好在一个单独的线程中重复使用它。如果在同一个BasicClientConnectionManager对象中,多次执行http请求,后继请求与先前请求是同一个route,那么BasicClientConnectionManager会使用同一个连接完成后续请求,否则,BasicClientConnectionManager会将先前的connection关闭,然后为后续请求创建一个新的连接。换句话说,BasicClientConnectionManager会尽力复用先前的连接(注意:创建连接和销毁连接都是不小的开销)