commons-pool 解析
首先抛出个常见的长连接问题:
1 都知道连接MySQL的应用中大多会使用框架例如 c3p0 ,dbcp proxool 等来管理数据库连接池。 数据库连接池毫无疑问都是采用长连接方式。 那么MySQL经典八小时问题为何产生?
我一开始的疑惑是既然是长连接必然有不停的心跳检测机制一直不停的骚扰者服务端, 那么服务端怎么还能检测到一个八个小时毫无动静的连接呢? 无非是因为八个小时内client端没有发送心跳包给服务器,不禁会问长连接会没有心跳包么?
2 又或者我们常用的SecureCRT工具,当我们想要保持终端一直畅通就需要设置以下参数,意味着60秒发一次心跳

3 再者用client list 观察我们的redis 服务器,会发现大量的ping请求, 那么这些心跳ping请求由谁来控制触发? 什么情况下会触发?

-------------------------------------------------------那么接下来是揭开谜底的序幕-------------耐心分析---------------------------------------------------------------------------------------------
我们常用的jedis-XXX.jar以及c3p0XX.jar底层都是使用 commons-pool来作为对象池的实现。下面以租车公司为例子说明这张图,介绍commons pool的基本工作方式:

图1
GenericObjectPool(租车公司)
作为租车公司,需要提供租车和收回归还的车辆的两个服务,同时它还要管理着所有的那些车辆,随着业务发展壮大,需要买新车;对于已经不能安全驾驶的车辆,需要将其销毁;同时还要定期对车辆进行安全检测等。
PooledObject(租车公司的所有车辆)
租车公司的车辆分为三类:空闲可租用的车辆(Idle Objects),已借出的车辆(Active Objects),认为已丢弃的车辆(Anandoned Objects)
Borrow Object(租车)
- A1: 世界那么大,一位年轻人想租辆车出去逛逛
- A2: 老板先看看有没有空闲的车
- A3.1: 如果有,则将最近归还的车借出去,并标记为已借出(Active),如果没有空闲的车了,就买辆,同时也标记为已借出(这是一家不差钱的公司)
- A3.2: 老板把标记好的车租给年轻人
Return Object(还车)
- B1: 世界那么大,年轻人终于逛完了,回来还车
- B2: 老板把车放回停车场,并把标记改为空闲状态(Idle),可以再被其他人租用。
TestOnBorrow/TestOnReturn(租出/归还时进行检查)
这家公司不仅不差钱,它对车辆的安全还很负责,对于租出去的车,不管是从空闲车辆里取出的,还是新买回的,都会先检查一遍这车的好坏,总不能坑了年轻人,如果发现有问题,立马再换一辆。归还的时候,也会检查一遍,如果有问题,就扔掉(真土豪),除此之外,公司还专门请了一位车辆安检员,定期对闲置了一段时间的车辆进行安全检测(Evict Thread),一有问题也扔掉。
有借有还,看上去一切都很美好。
然而现实里总有意外发生:
年轻人借走车后,发现世界越逛越大,久久不愿回家。 安检员定期检查时发现这车子都借出去大半年了,还没还回来,是不是丢了?于是掏出手机,”啪“的按了一下,远程将车子熄了火,标记为报废车辆(Abandoned),当作报废处理了。
Evict Thread(定期检查的安检人员)
- C1: 对于已归还标记为空闲的车辆,安检员定期对它们抽查,如果超过一段时间没有使用,看看是否坏掉,坏了就及时作废掉(C2).
- D1: 对于标记为已借出的对象,安检员定期检查时发现借出很久都未还,直接作废(D2)。
好了,故事讲完了,希望大家对Commons Pool都理解了。
------------------------------------------------------------------接下来剖析Commons-pool关键源码-----------------耐心分析----------------------------------------------------------------------------------------------------------------------------------------------
为了简单起见,该图只表现继承和实现关系

图2
上层的jar应用大部分情况下我们只使用ObjectPool和PoolableObjectFactory的相关实现类, 例如jedisFactory 继承的是BasePoolableObjectFactory(典型工厂设计模式), 其pool直接实例化GenricObjectPool类 。
下面以jedis为例简单说明JedisFactory干了啥事:
1 对象如何创建
jedis的JedisFactory继承了org.apache.commons.pool.BasePoolableObjectFactory实现了makeObject()方法 :
public Object makeObject() throws Exception {
final Jedis jedis = new Jedis(this.host, this.port, this.timeout);
// 开始进行socket连接
jedis.connect();
if (null != this.password) {
jedis.auth(this.password);
}
if( database != 0 ) {
jedis.select(database);
}
return jedis;
}
这段代码很好懂,无非就是底层开启个socket 开始连接对应的服务器, 例如最底层的代码:
public void connect() {
if (!isConnected()) {
try {
socket = new Socket();
//->@wjw_add
socket.setReuseAddress(true);
socket.setKeepAlive(true); //Will monitor the TCP connection is valid
socket.setTcpNoDelay(true); //Socket buffer Whetherclosed, to ensure timely delivery of data
socket.setSoLinger(true,0); //Control calls close () method, the underlying socket is closed immediately
//<-@wjw_add
socket.connect(new InetSocketAddress(host, port), timeout);
socket.setSoTimeout(timeout);
outputStream = new RedisOutputStream(socket.getOutputStream());
inputStream = new RedisInputStream(socket.getInputStream());
} catch (IOException ex) {
throw new JedisConnectionException(ex);
}
}
}
2 activateObject 激活对象
public void activateObject(Object obj) throws Exception {
if (obj instanceof Jedis) {
final Jedis jedis = (Jedis)obj;
if (jedis.getDB() != database) {
jedis.select(database);
}
}
}
3 destroyObject 销毁对象
public void destroyObject(final Object obj) throws Exception {
if (obj instanceof Jedis) {
final Jedis jedis = (Jedis) obj;
if (jedis.isConnected()) {
try {
try {
jedis.quit();
} catch (Exception e) {
}
jedis.disconnect();
} catch (Exception e) {
}
}
}
}
4 validateObject 检测对象(对象借入借出时如果需要检测此对象的健康状况,或者定时对池对象进行evict)
public boolean validateObject(final Object obj) {
if (obj instanceof Jedis) {
final Jedis jedis = (Jedis) obj;
try {
return jedis.isConnected() && jedis.ping().equals("PONG");
} catch (final Exception e) {
return false;
}
} else {
return false;
}
}
只做了上述四件事情,告诉对象池(GenricObjectPool) 如何创建对象,如何激活对象, 如何销毁对象,以及用什么方式做心跳检测。结合图1的池对象流转图
接下来分析GenricObjectPoo里面的几个功能: 对象的借出:borrowObject(), 对象的归还:returnObject() ,以及对象池的后台管理:Evictor定时
这里参考一篇文章:http://deyimsf.iteye.com/blog/2119488
此文章基本上很好的论述了源码关键部位。 接下来分析下 idel 时间是怎么算出来的, 以及作用是什么
A : 一个对象的出生时打上个系统时间--------borrowObject()方法或者evict()方法的--->ensureMinIdle()方法(维持最小的idel个对象)
borrowObject方法示例:
boolean newlyCreated = false;
if(null == latch.getPair()) {
try {
T obj = _factory.makeObject();
// 此刻创建一个新生命, new ObjectTimestampPair是拿当前的系统时间当此生命的生日
latch.setPair(new ObjectTimestampPair<T>(obj));
newlyCreated = true;
} finally {
if (!newlyCreated) {
// object cannot be created
synchronized (this) {
_numInternalProcessing--;
// No need to reset latch - about to throw exception
}
allocate();
}
}
}
ensureMinIdle()方法示例
private void ensureMinIdle() throws Exception {
// this method isn't synchronized so the
// calculateDeficit is done at the beginning
// as a loop limit and a second time inside the loop
// to stop when another thread already returned the
// needed objects
// 上面英文注释很明确
int objectDeficit = calculateDeficit(false);
for ( int j = 0 ; j < objectDeficit && calculateDeficit(true) > 0 ; j++ ) {
try {
// 跟进去会发现此处就是不断创建新生命,如果满足条件就放入池中
addObject();
} finally {
synchronized (this) {
_numInternalProcessing--;
}
allocate();
}
}
}
B : 一个对象使用完归来时再次打上个系统时间--------returnObject()方法的addObjectToPool方法代码片段
synchronized (this) {
if (isClosed()) {
shouldDestroy = true;
} else {
if((_maxIdle >= 0) && (_pool.size() >= _maxIdle)) {
shouldDestroy = true;
} else if(success) {
// borrowObject always takes the first element from the queue,
// so for LIFO, push on top, FIFO add to end
if (_lifo) {
// 此处对归来的obj 对象打上系统时间并放入对首。 last in first out 算法是jedis 默认算法
_pool.addFirst(new ObjectTimestampPair<T>(obj));
} else {
_pool.addLast(new ObjectTimestampPair<T>(obj));
}
if (decrementNumActive) {
_numActive--;
}
doAllocate = true;
}
}
}
c : 当给minEvictableIdleTimeMillis这个参数配置上一个大于0 的数,意味着对象空闲了多久就被会干掉, 例如evict()代码片段
// 此处去算此对象空闲了多久,接下来判断需不需要把此对象干掉
final long idleTimeMilis = System.currentTimeMillis() - pair.tstamp;
if ((getMinEvictableIdleTimeMillis() > 0) &&
(idleTimeMilis > getMinEvictableIdleTimeMillis())) {
removeObject = true;
} else if ((getSoftMinEvictableIdleTimeMillis() > 0) &&
(idleTimeMilis > getSoftMinEvictableIdleTimeMillis()) &&
((getNumIdle() + 1)> getMinIdle())) { // +1 accounts for object we are processing
removeObject = true;
}
if(getTestWhileIdle() && !removeObject) {
boolean active = false;
try {
_factory.activateObject(pair.value);
active = true;
} catch(Exception e) {
removeObject=true;
}
if(active) {
if(!_factory.validateObject(pair.value)) {
removeObject=true;
} else {
try {
_factory.passivateObject(pair.value);
} catch(Exception e) {
removeObject=true;
}
}
}
}
OK 以上三段代码讲述完毕。 阐述了对象的idel 时间的由来以及用处。 个人建议把minEvictableIdleTimeMillis设置成-1
---------------------------------------------------来个使用总结吧-----------------------------------------------------------------------------------------------------------------------------------
redis 使用注意事项:
1 redis server 的time_out(单位是S清除idel多久的客户端连接)最好不要设置成一个大于0的时间。
设置成0即不加限制的隐患 :当客户端此刻与服务端断网, 而http请求依然不断,那么当一个请求从池中取得一个连接(连接池此刻remove一个连接), 接着调用validateObject()方法发现ping出错, 紧接着就会调用destroyObject 进行对象的销毁(由于断网客户端的quit命令压根发送不到服务器端), 服务器端就会出现一堆僵尸进程。
2 服务器端如果设置了idel>0的空闲时间, 那么客户端最好设置上对应的心跳频率即多久心跳一次,
timeBetweenEvictionRunsMillis=60000 // 多久跑一次任务
pool.minEvictableIdleTimeMillis=-1 // idel 多久的对象就会被清理掉 , -1意味着无论空着多久都没事。 实际上上面的那个定时任务没跑一次,被定位到的线程都要去ping一次服务器,所以对象的空闲时间也就是60S。
这里面还涉及到每一次清理定时任务去清理多少个对象的问题。 一次清理的对象不要太少(或者走默认配置隔一定时间, 对象池里面的空闲对象全部心跳一次)即以下配置(不配置会走默认配置即全部检查):
numTestsPerEvictionRun // 驱逐器每次运行时检查池中闲置对象的最大个数 ,(比如该值设置为3,此时池中有5个闲置对象,那么每次只会检查前三个闲置对象。
所以也就回答了mysql 八小时问题, 就是因为服务器端设置了一个超时时间,而客户端却没有配置上定时心跳检测的功能,导致八个小时之后被服务器端干掉。
commons-pool 解析的更多相关文章
- Tomcat 开发web项目报Illegal access: this web application instance has been stopped already. Could not load [org.apache.commons.pool.impl.CursorableLinkedList$Cursor]. 错误
开发Java web项目,在tomcat运行后报如下错误: Illegal access: this web application instance has been stopped already ...
- NoClassDefFoundError: org/apache/commons/pool/impl/GenericObjectPool
错误:Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/pool/impl ...
- Spring + Tomcat 启动报错java.lang.ClassNotFoundException: org.apache.commons.pool.impl.GenericObjectPool
错误如下: -- ::,-[TS] INFO http-- org.springframework.beans.factory.support.DefaultListableBeanFactory - ...
- Apache Commons Pool 故事一则
Apache Commons Pool 故事一则 最近工作中遇到一个由于对commons-pool的使用不当而引发的问题,习得正确的使用姿势后,写下这个简单的故事,帮助理解Apache Commons ...
- 池化 - Apache Commons Pool
对于那些创建耗时较长,或者资源占用较多的对象,比如网络连接,线程之类的资源,通常使用池化来管理这些对象,从而达到提高性能的目的.比如数据库连接池(c3p0, dbcp), java的线程池 Execu ...
- org/apache/commons/pool/impl/GenericObjectPool异常的解决办法
org/apache/commons/pool/impl/GenericObjectPool异常的解决办法 webwork+spring+hibernate框架的集成, 一启动Tomcat服务器就出了 ...
- 对象池化技术 org.apache.commons.pool
恰当地使用对象池化技术,可以有效地减少对象生成和初始化时的消耗,提高系统的运行效率.Jakarta Commons Pool组件提供了一整套用于实现对象池化的框架,以及若干种各具特色的对象池实现,可以 ...
- JedisCluster中应用的Apache Commons Pool对象池技术
对象池技术在服务器开发上应用广泛.在各种对象池的实现中,尤其以数据库的连接池最为明显,可以说是每个服务器必须实现的部分. apache common pool 官方文档可以参考:https://c ...
- Cache Lucene IndexReader with Apache Commons Pool
IndexReaderFactory.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 2 ...
- Java_异常_03_ java.lang.NoClassDefFoundError: org/apache/commons/pool/KeyedObjectPoolFactory
异常信息: java.lang.NoClassDefFoundError: org/apache/commons/pool/KeyedObjectPoolFactory 原因: 我用的是commons ...
随机推荐
- XAML属性赋值转换之谜(WPF XAML语法解密)
XAML与XML类似,就是XML延伸过来的.为了更好的表达一些功能,WPF对XML做了扩展,有些功能是WPF在后台悄悄的替你做了.有时候,虽然实现了某个功能,但是对实现原理还是很茫然.今天就讲讲XAM ...
- logstash-1-安装配置
centos logstash logstash logstash是什么呢, 他是一个数据管道, JRuby编写的运行在java虚拟机的具有收集, 分析和转发数据流功能的工具 特性: 安装 1), w ...
- 软件架构设计学习总结(3):QQ空间技术架构之详解
QQ空间作为腾讯海量互联网服务产品,经过近七年的发展,实现了从十万级到亿级同时在线的飞跃.在这个过程中,QQ空间团队遇到了哪些技术挑战?其站点前后台架构随着业务规模的变化又进行了怎样的演进与变迁?成长 ...
- 数据结构与算法(周鹏-未出版)-第六章 树-6.5 Huffman 树
6.5 Huffman 树 Huffman 树又称最优树,可以用来构造最优编码,用于信息传输.数据压缩等方面,是一类有着广泛应用的二叉树. 6.5.1 二叉编码树 在计算机系统中,符号数据在处理之前首 ...
- 解决U盘拷贝时提示文件过大问题(不能拷贝超过4个g的文件)
为什么一个16G的U盘却拷不进一个4G大点的文件呢,想必很多朋友们都在疑问? 其实这跟U盘的磁盘格式有关,目前市面上常用的U盘大都是FAT32格式,我们可以查看U盘属性. 那么FAT32是什么呢? F ...
- 【转载】H5页面列表的无线滚动加载(前端分页)
本代码基于Vue的架构 1.首先,要滚动的内容放在一个‘id=box’ 的div里,对div进行scroll事件的监听 <div id="box" class="m ...
- .NET平台开源文档与报表处理组件包括Execel PDF Word等
在前2篇文章这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧 和这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧!(第二辑)中,大伙热情高涨.再次拿出自己的私货,在.NET平台 ...
- maven的注意点
一.dependency的scope 取值范围:compile.test.runtime.provided.system compile 默认就是compile,什么都不配置也就是意味着compile ...
- 愿你走出半生,归来仍是Java Parser
几天前,我的一个朋友给了我一个Haskell问题 Hey, MK,假设我有个BNF,并且我在Haskell中有个这个BNF的parser.现在,我想给这个BNF改一行,有没有办法不用动这个BNF pa ...
- Logback 日志持久化
Logback是log4j的增强版,比log4j更具灵活,其提供了将日志输出到数据库的功能,本文将介绍如何将指定的日志输出到mysql中. 一.自定义log标志 由于Logback原生的配置会将所有的 ...