起因
頻繁的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;
}