boneCP原理研究

** 轉載請註明源連接:http://www.cnblogs.com/wingsless/p/6188659.htmlhtml

boneCP是一款關注高性能的數據庫鏈接池產品 github主頁java

不過最近做者好像沒有心思更新了,由於他發現了一款更快的鏈接池產品,可是這不影響我學習它。git

鏈接的生存時間

MySQL有一個重要的參數wait_timeout,用於規定一個connection最大的idle時間,默認是28800秒,即每一個connection連續的sleep狀態不能超過該值,不然MySQL會自動回收該connection。github

鏈接池的做用是管理鏈接,任何想要請求數據庫鏈接的行爲都和鏈接池發生交互,從鏈接池裏申請鏈接,使用完成後將鏈接交還給鏈接池。數據庫

在一個比較空閒的系統上,鏈接可能長時間的處於sleep狀態,那麼一旦達到了MySQL wait_timeout的規定時間,MySQL就要回收鏈接,這時鏈接池若是仍然認爲該connection可用,待應用向鏈接池請求時,就會將不存在的connection資源交給應用,這樣就會報錯。安全

所以鏈接池須要一套機制保證每個connection在鏈接池生存期內是始終可用的。less

我推測應該有兩種機制:按期檢測和申請時檢測。性能

boneCP採用按期檢測機制,即每隔一個時間間隔就會檢查其管理的鏈接處於sleep狀態的時間,若是超過了設定的值,則會將此connection重建。學習

boneCP中有兩個很重要的參數:idleConnectionTestPeriodInSeconds和idleMaxAgeInSeconds,分別是鏈接探測時間閾值和鏈接最大空閒時間,默認值爲4小時和1小時。ui

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來講是不安全的,請求了一個以後新建一個給以後的請求作準備。

*轉載請註明源連接:http://www.cnblogs.com/wingsless/p/6188659.html

相關文章
相關標籤/搜索