HttpComponents分析之连接池实现 - jinspire - 博客园
早期的Http是这样的,一次http请求完成后,立即关闭连接。如果请求的数据非常少而次数又极多,那么通讯效率是非常低的。如何提高通讯的效率呢?其实很简单,只需在建立连接后,完成通话先等待一段时间,看对方在这段时间内是否还有话说,如果有话说,那么继续通信,否则过了这段时间后就关闭连接。这种解决方案在Http协议中也有体现,即keep-alive。
回到主题,Http协议是互联网上最流行的协议,webservices,基于网络的应用等在增加Http协议支持的需求同时,也强有力的推动协议本身从浏览器应用的局限性场合扩张出来。虽然java.net对http的协议从网络上获取资源等功能做了基本的支持,但它并不能满足许多应用对协议全面功能和灵活性的要求。比如下面提到的http连接池就是一个非常重要的功能。
Http连接池是利用了Http 1.1 KeepAlive的持久连接特性,在TCP协议里两个机器建立连接涉及三次握手,是比较消耗时间的,特别是在持续传送少量数据时,如果连接能够持续重用,就可以达到较大的吞吐量。同时也要考虑到如果可以对于一个服务器端口开通多个socket连接去传输信息,是可以达到网络带宽的一定提高的。用流行的 一句话体来说,就是管理一个路由的多个持久连接的创建,分配,复用,回收问题。
图1:connPool的继承体系
一、基本结构:
1. ConnPool:连接池接口:
- Future<E> lease(final T route, final Object state, final FutureCallback<E> callback);
从连接池中取出连接
- void release(E entry, boolean reusable);
释放连接
2. AbstractConnPool:
其中主要有如下数据结构:
- Map<T, RouteSpecificPool<T, C, E>> routeToPool 每个路由对应其连接池的映射,
- Set<E> leased总池出借的连接集合,LinkedList<E> available 总池可用的连接集合,
- LinkedList<PoolEntryFuture<E>> pending总池等待取连接的队列,
- Map<T, Integer> maxPerRoute 每个路由的最大连接数量映射表。
- int maxTotal 总池的连接最大数。
并依靠上述数据结构进行了资源同步和生命周期方法如shutdown()等操作。
图2:routeToPool结构图
其中routeToPool里面不同的路由有各自的RouteSpecificPool(路由相关连接池),其中也有三个数据结构:
- Pending 同路由等待取连接的队列
- Avaliable 同路由可以使用的连接队列。
- Leased:同路由已经租赁使用的连接集合。
二、主要操作分析
Lease 租赁连接:
1. 先从routeToPool找到当前路由对应的连接池pool
2. 再去连接池pool找空闲的连接,并观察其是否是关闭或者超时的连接,是则将其关闭,并再查找下一个空闲连接,直到找到或者遍历完可用连接链表avaliable为止。
3. 如果找到,则在可用连接链表avaliable中移除entry,并将其加入到租赁集合Leased中去,并返回。
4. 如果找不到,那么就查询每个路由连接最大上限映射表maxPerRoute找到当前路由最大上限maxPerRoute,并检查当前路由连接数+1的方案是否超过了本路由最大上限,如果超过,则将此路由对应的连接池 avaliable队列中最早使用的连接关闭。
5. 检查当前路由对应的池中已有的连接数是否超过上限maxPerRoute且已有的连接总数是否小于总池中的最大计数maxTotal;
如果满足条件,再检查avaliable队列中连接的数量是否大于等于总池可分配连接数,满足则尝试从总池可用连接链表avaliable中选取最早入队的连接,并在此连接相应的路由对应的池中进行连接关闭;
最后创建新的连接并加入总池和路由对应的leased集合中,创建成功则返回。
6. 如果步骤5种条件不满足,那么将其加入到等待队列pending中去,并进入等待模式。
7. 如有人唤醒后再检查超时,如果没有超时则跳回到2。
Release归还连接:
1. 先从总池中归还连接
2. 如果1成功,再从路由对应的连接归还
3. 最后通知等待唤醒取连接的pending队列中的任务继续去获取连接。