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

相关文章
相关标签/搜索