起因
频繁的connection close/open.
DBA点名:有个component从3个礼拜前突然开始频率的打开/关闭db的connection,大大抵消了使用connection pool的好处
调查
经探索发现,该component使用Torque来进行db connection pool的管理。而Torque又使用DBCP来管理connection pool。
而DBCP实际上又使用Object Pooling来。
在DBCP 的configuration中有这样的提示
If maxIdle is set too low on heavily loaded systems it is possible you will see connections being closed and almost immediately new connections being opened. This is a result of the active threads momentarily closing connections faster than they are opening them, causing the number of idle connections to rise above maxIdle. The best value for maxIdle for heavily loaded system will vary but the default is a good starting point.
这个现象和我们遇到的问题完全一致。然后重新check下产品上Torque的配置:
torque.dsfactory.XXX.pool.maxIdle=6
torque.dsfactory.XXX.pool.maxActive=30
毫无疑问,maxIdle配置有误。DBCP的默认配置maxIdle和maxActive大小相同。
转到GenericKeyedObjectPool的javadoc
param | Descritpion | Usage |
---|---|---|
maxActive | 最大活动对象数目 | active是指被client正在使用的对象;可被认为是对象(资源)数目的峰值 |
maxIdle | 最大空闲对象数目 | 对象返回到Pool时,Pool的最大size;可被认为是对象(资源)数目的平均值 |
timeBetweenEvictionRunsMillis | 回收任务的运行间隔 | 默认是每5分钟检查一个Pool是否有对象Idle > minEvictableIdleTimeMillis |
minEvictableIdleTimeMillis | 空闲对象的存活时间 | 默认如果该对象1小时内没有被使用,则认为可以被回收,比如真正的释放数据库连接 |
minIdle | 最小空闲对象数目 | 对象返回到Pool时/Pool初始化时,Pool的最小size |
Common Pool 1.3 源码解析
long starttime = System.currentTimeMillis();
boolean newlyCreated = false;
for(;;)
{
LinkedList pool = (LinkedList)(_poolMap.get(key)); //Pool为链接结构
pair = (ObjectTimestampPair)(pool.removeFirst()); //取pool中队头元素
if(null == pair)
{
int active = getActiveCount(key); //当前active数目,被client正在使用的对象数目
if ((_maxActive < 0 || active < _maxActive) //小于最大active,创建新的对象
{
Object obj = _factory.makeObject(key);
pair = new ObjectTimestampPair(obj);
newlyCreated = true;
}
else
{
switch(_whenExhaustedAction) //根据exhaust策略来创建,抛异常,或等待
{
case WHEN_EXHAUSTED_GROW:
Object obj = _factory.makeObject(key);
pair = new ObjectTimestampPair(obj);
break;
case WHEN_EXHAUSTED_FAIL:
throw new NoSuchElementException();
case WHEN_EXHAUSTED_BLOCK:
if(_maxWait <= 0)
{
wait(); //不限时等待
}
else
{
final long elapsed = (System.currentTimeMillis() - starttime);
final long waitTime = _maxWait - elapsed;
if (waitTime > 0)
{
wait(waitTime); //限时等待
}
}
//被唤醒或者限时已到
if(_maxWait > 0 && ((System.currentTimeMillis() - starttime) >= _maxWait))
{
throw new NoSuchElementException("Timeout waiting for idle object");//timeout异常
}
else
{
continue; // 重新尝试获取对象
}
}
}
incrementActiveCount(key); //active count++
}
}
returnObject
LinkedList pool = (LinkedList) (_poolMap.get(key));
decrementActiveCount(key); // active count--
if(_maxIdle >= 0 && (pool.size() >= _maxIdle)) //pool中已有足够闲置对象
{
shouldDestroy = true; //直接销毁
}
else
{
pool.addLast(new ObjectTimestampPair(obj)); //放入队尾,更新对象访问时间戳
}
notifyAll(); //唤醒borrowObject中等待的线程
if(shouldDestroy)
{
_factory.destroyObject(key, obj); //销毁对象
}
看完上面两个方法,真相已经大白。
- common pool采用链接队列(LinkedList)存放Idle 对象,其pool命名为idlePool更为合适;borrowObject从队列移除对象,returnObject(或者初始化)放对象到队列中。
- 队列采用先前先出模式(队头取,队尾放),更老的对象先取出。
- 采用acitve count进行计数,统计client正在使用的对象数目;Idle不需要另外计数, pool size即为idle数目
- 假定初始为空,borrowObject创建14个对象;
- 一半的资源(7)释放被同时释放,多余的1个对象会被销毁。
- borrow 8个对象;pool中有6个idle,2个新的对象被创建。
- 9个对象同时被释放,这时多余的3个对象被销毁。
- 。。。
final LinkedList list = (LinkedList)_poolMap.get(key);
if (_evictLastIndex < 0 || _evictLastIndex > list.size()) {
_evictLastIndex = list.size(); //不同的pool共用_evictLastIndex
}
objIter = list.listIterator(_evictLastIndex); // 从队尾开始
ObjectTimestampPair pair = (ObjectTimestampPair)(objIter.previous()); //使用list iterator
boolean removeObject=false
if(_minEvictableIdleTimeMillis > 0 &&
System.currentTimeMillis() - pair.tstamp > _minEvictableIdleTimeMillis) //闲置过长
{
removeObject=true;
}