轉載請標明鏈接:http://www.cnblogs.com/wingsless/p/6349434.html boneCP連接的實現 boneCP自己實現了標準的java.sql.Connection介面,除了會持有Connection對象之外,還會擁有一些屬性用於標記連接的創建時間,空閑時間等。 ...
轉載請標明鏈接:http://www.cnblogs.com/wingsless/p/6349434.html
boneCP連接的實現
boneCP自己實現了標準的java.sql.Connection介面,除了會持有Connection對象之外,還會擁有一些屬性用於標記連接的創建時間,空閑時間等。
比較重要的時間概念代碼如下:
if (!recreating){
//上次使用時間戳
connectionLastUsedInMs = System.currentTimeMillis();
//上次重置時間戳
connectionLastResetInMs = System.currentTimeMillis();
//連接創建時間
connectionCreationTimeInMs = System.currentTimeMillis();
}
boneCP對連接的管理
MySQL對連接有最大空閑時間的限制,預設是8小時,因此連接池在將連接分配給客戶端時,應該保證連接的可用性。
一般會有兩種做法:分配時測試和定時測試。
分配時測試:在收到客戶端請求時,連接池首先對向資料庫發送一條簡單的SQL,判斷連接是否可用。
定時測試:啟動一個測試線程(ConnectionTesterThread),定時每隔一段時間向資料庫發送命令,判斷連接是否可用。
boneCP採用定時測試的方式保證連接的可用。為了實現該方式,boneCP規定了兩個重要的參數:idleConnectionTestPeriodInSeconds(預設4小時)和idleMaxAgeInSeconds(預設1小時),分別表示空閑連接探測周期和連接最大可空閑時間。
預設情況下,boneCP啟動的keepalive線程每個1小時會啟動一次,用於檢查連接是否達到了空閑時間的上限:
代碼片段1:
if (connection.isPossiblyBroken() ||
((this.idleMaxAgeInMs > 0) && ( System.currentTimeMillis()-connection.getConnectionLastUsedInMs() > this.idleMaxAgeInMs))){
// kill off this connection - it's broken or it has been idle for too long
closeConnection(connection);
continue;
}
如果一個線程距離上次使用已經過去了1小時以上,則會在這段邏輯中被close掉,然後繼續迴圈掃描其他的連接。
一個連接被close掉之後,boneCP會有其他的線程負責新建連接。因此表現在MySQL客戶端上,可以看到每隔1小時,就會關閉一些連接並出現一些新的連接(極端情況下,所有的連接都被關閉,並一次性重建所有連接)。註意新建連接的MySQL分配id和舊連接完全不同。
需要註意的是,在預設情況下,並沒有觀察到邏輯執行到這裡的現象:
代碼片段2:
if (this.idleConnectionTestPeriodInMs > 0 && (currentTimeInMs-connection.getConnectionLastUsedInMs() > this.idleConnectionTestPeriodInMs) &&
(currentTimeInMs-connection.getConnectionLastResetInMs() >= this.idleConnectionTestPeriodInMs)) {
// send a keep-alive, close off connection if we fail.
if (!this.pool.isConnectionHandleAlive(connection)){
closeConnection(connection);
continue;
}
// calculate the next time to wake up
tmp = this.idleConnectionTestPeriodInMs;
if (this.idleMaxAgeInMs > 0){ // wake up earlier for the idleMaxAge test?
tmp = Math.min(tmp, this.idleMaxAgeInMs);
}
}
這段邏輯主要判斷是否有連接的上一次重置時間距現在超過4小時,如果有,則向MySQL發一個探測命令,並且將連接的最後一次重置時間設為當前時間,如果連接alive,返回true,不對連接進行close操作。
上一段代碼也是ConnectionTesterThread的邏輯,推斷應該是因為每隔1小時,連接就會被關閉重建一次,因此不會存在滿足這段邏輯條件的連接存在。
如果修改預設值,將idleConnectionTestPeriodInSeconds和idleMaxAgeInSeconds的值對調,那麼boneCP仍會每隔1小時(即idleConnectionTestPeriodInSeconds時間)定時調度keepalive線程。
此時可以發現上述兩段邏輯都會被執行,每次執行的時候,都會首先執行代碼片段2中的邏輯,因此每次都會更新ConnectionHandler的最後一次重置時間,但是連接仍然不會生存超過4小時,每4小時,邏輯就會進入代碼片段1中,將連接close掉。
jdbc驅動的NonRegistingDriver分析
現在發現系統運行一段時間以後就會出現fullGC,從記憶體分析上看,大部分記憶體都被com.mysql.jdbc.NonRegistingDriver占去。通過跟蹤jdbc代碼發現,當connection建立的時候,jdbc總會將該connection交給NonRegistingDriver,建立一個虛引用,並將該虛引用放在一個ConcurrentHashMap中。
代碼片段3:
protected static void trackConnection(Connection newConn) {
ConnectionPhantomReference phantomRef = new ConnectionPhantomReference((ConnectionImpl) newConn, refQueue);
connectionPhantomRefs.put(phantomRef, phantomRef);
}
記憶體分析中發現很多記憶體正是被NonRegistingDriver中的ConcurrentHashMap占去,因此可以推斷,應該是新建了大量的Connection導致了大量的NonRegistingDriver對象被新建,從而引發了記憶體問題。
綜合上面對boneCP的分析,應該是boneCP定時的將連接close掉再重建導致的,如果在不是很繁忙的系統上,該情況應該會比較嚴重。
boneCP探測連接可用的方式
在沒有設置探測SQL的情況下,boneCP利用jdbc的getMetaData方法,獲取connection的元數據,從其Javadoc上看,元數據應該包括了資料庫的表,SQL語法,存儲過程等等信息:
The metadata includes information about the database's tables, its supported SQL grammar, its stored procedures, the capabilities of this connection, and so on.
經過抓包分析,實際上getMetaData方法向MySQL 發送了一條簡單的show tables命令,如果收到response則認為連接是alive的。
轉載請標明鏈接:http://www.cnblogs.com/wingsless/p/6349434.html