** 轉載請注明源鏈接:http://www.cnblogs.com/wingsless/p/6188659.html
boneCP是一款關注高性能的數據庫連接池產品 github主頁 。
不過最近作者好像沒有心思更新了,因為他發現了一款更快的連接池產品,但是這不影響我學習它。
連接的生存時間
MySQL有一個重要的參數wait_timeout,用於規定一個connection最大的idle時間,默認是28800秒,即每個connection連續的sleep狀態不能超過該值,否則MySQL會自動回收該connection。
連接池的作用是管理連接,任何想要請求數據庫連接的行為都和連接池發生交互,從連接池里申請連接,使用完成后將連接交還給連接池。
在一個比較空閑的系統上,連接可能長時間的處於sleep狀態,那么一旦達到了MySQL wait_timeout的規定時間,MySQL就要回收連接,這時連接池如果仍然認為該connection可用,待應用向連接池請求時,就會將不存在的connection資源交給應用,這樣就會報錯。
因此連接池需要一套機制保證每一個connection在連接池生存期內是始終可用的。
我推測應該有兩種機制:定期檢測和申請時檢測。
boneCP采用定期檢測機制,即每隔一個時間間隔就會檢查其管理的連接處於sleep狀態的時間,如果超過了設定的值,則會將此connection重建。
boneCP中有兩個很重要的參數:idleConnectionTestPeriodInSeconds和idleMaxAgeInSeconds,分別是連接探測時間閾值和連接最大空閑時間,默認值為4小時和1小時。
boneCP會啟動三個線程來進行定期任務:
this.keepAliveScheduler = Executors.newScheduledThreadPool(this.config.getPartitionCount(), new CustomThreadFactory("BoneCP-keep-alive-scheduler"+suffix, true));
this.maxAliveScheduler = Executors.newScheduledThreadPool(this.config.getPartitionCount(), new CustomThreadFactory("BoneCP-max-alive-scheduler"+suffix, true));
this.connectionsScheduler = Executors.newFixedThreadPool(this.config.getPartitionCount(), new CustomThreadFactory("BoneCP-pool-watch-thread"+suffix, true));
其中keepAliveScheduler用來每隔一段時間檢查connection的sleep狀態時間是否達到了idleMaxAgeInSeconds或者是否已經失效,如果是,則會將連接kill掉,主要的邏輯見ConnectionTesterThread.java。
需要注意的是,在默認的配置下,該檢查的調度是這樣定義的:
this.keepAliveScheduler.scheduleAtFixedRate(connectionTester,delayInSeconds, delayInSeconds, TimeUnit.SECONDS);
此時delayInSeconds的值是取idleMaxAgeInSeconds值,即1小時,實際上只要idleMaxAgeInSeconds小於idleConnectionTestPeriodInSeconds,則delayInSeconds一定是idleMaxAgeInSeconds。這樣,該線程每1小時就會啟動一次,同時要檢查時間閾值就會變成1小時。
keepalive線程要執行的命令是這樣的:
final Runnable connectionTester = new ConnectionTesterThread(connectionPartition, this, this.config.getIdleMaxAge(TimeUnit.MILLISECONDS), this.config.getIdleConnectionTestPeriod(TimeUnit.MILLISECONDS), queueLIFO);
下面是對ConnectionTesterThread類的解析:
protected ConnectionTesterThread(ConnectionPartition connectionPartition,
BoneCP pool, long idleMaxAgeInMs, long idleConnectionTestPeriodInMs, boolean lifoMode){
this.partition = connectionPartition;
this.idleMaxAgeInMs = idleMaxAgeInMs; //60 * 60 * 1000
this.idleConnectionTestPeriodInMs = idleConnectionTestPeriodInMs; //240 * 60 * 1000
this.pool = pool;
this.lifoMode = lifoMode;
}
上面代碼中,兩個主要時間用注釋標明。
在該類中最重要的兩段代碼是用來判斷connection的sleep時間是否超標的,只需要對比現在時間和connection最后一次使用時間中間的時間間隔就可以:
// check if connection has been idle for too long (or is marked as broken)
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;
}
上一段代碼判斷connection是否sleep時間超過了idleMaxAgeInMs的規定(默認3600000ms)。
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);
}
}
如果保持默認值,或者設置的值中idleConnectionTestPeriodInMs大於idleMaxAgeInMs的,則這段代碼理論上講不會執行。但是無論如何,這個類都是用來殺死connection的。
接下來繼續看BoneCP類,在確定了keepalive調度之后(沒有設置maxConnectionAgeInSeconds參數),程序會繼續執行到
this.connectionsScheduler.execute(new PoolWatchThread(connectionPartition, this));
這段代碼最主要的目的就是新建connection。如果沒有設置,則會根據partition最大連接數和已創建連接數進行計算,決定下次新建的連接數。
至於一次新建多少連接,由該類PoolWatchThread.java中的這句決定:
fillConnections(Math.min(maxNewConnections, this.partition.getAcquireIncrement()));
而重要的參數maxNewConnections則是這樣計算出來的:
maxNewConnections = this.partition.getMaxConnections()-this.partition.getCreatedConnections();
根據我對代碼debug的情況看,連接新建無非分兩個情況:keepalive殺死超時連接后和調用getConnection方法后。
讓人奇怪的是為什么連接里明明有sleep的連接,卻非要新建一個給客戶端用?
跟代碼發現了這個類:DefaultConnectionStrategy.java
if (!connectionPartition.isUnableToCreateMoreTransactions()){ // unless we can't create any more connections...
this.pool.maybeSignalForMoreConnections(connectionPartition); // see if we need to create more
}
注釋中寫了,除非是沒辦法新建連接了(因為到了上限),否則就會新建連接,此時需要了解maybeSignalForMoreConnections這個方法是干什么的?
於是又要回到BoneCP類:
/**
* Tests if this partition has hit a threshold and signal to the pool watch thread to create new connections
* @param connectionPartition to test for.
*/
protected void maybeSignalForMoreConnections(ConnectionPartition connectionPartition) {
if (!connectionPartition.isUnableToCreateMoreTransactions()
&& !this.poolShuttingDown &&
connectionPartition.getAvailableConnections()*100/connectionPartition.getMaxConnections() <= this.poolAvailabilityThreshold){
connectionPartition.getPoolWatchThreadSignalQueue().offer(new Object()); // item being pushed is not important.
}
}
好了,發現問題關鍵了:
connectionPartition.getAvailableConnections()*100/connectionPartition.getMaxConnections() <= this.poolAvailabilityThreshold
這個判斷很重要,poolAvailabilityThreshold這個值是0,這是默認值,也沒設置過,由此可以推斷connectionPartition.getAvailableConnections()一定為0(分母不可能為0,所以分子肯定是0)。
再看看connectionPartition.getAvailableConnections()是干什么的,從名字上看是獲得可用連接的,那么明顯沒有獲得可用連接:
protected int getAvailableConnections() {
return this.freeConnections.size();
}
freeConnections是一個Queue,它的size現在來看應該是0,也就是說沒有free的connection。
但是明明初始化連接池的時候已經建立了連接的,而且我是調試程序,根本沒有可能使用到已經建立的連接,為什么呢?
這個情況只在只建立了一個連接的時候才會出現,這是因為這個類DefaultConnectionStrategy.java的這句:
result = connectionPartition.getFreeConnections().poll();
在這一句執行之前,connectionPartition中的freeConnection屬性中是有一個連接的,但是在poll執行之后,該連接被取出,因此freeConnection屬性變成了一個空的Queue,size自然就是0了。所以該情況在最開始有一個以上連接的時候就不再存在了。
這也是正常的,因為只有一個connection對於pool來說是不安全的,請求了一個之后新建一個給之后的請求做准備。