boneCP原理研究


** 轉載請注明源鏈接: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來說是不安全的,請求了一個之后新建一個給之后的請求做准備。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM