Druid源码分析(六) testOnBorrow,testOnReturn,testWhileIdle

前言

针对从Druid连接池中获取连接,归还连接的三个参数进行了源码阅读与分析

属性定义

属性 默认值 备注
testOnBorrow false 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testOnReturn false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testWhileIdle false 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis 1分钟(1.2.8) 有两个含义:1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明

testOnBorrow

入口

public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException 

作用

从数据库链接池中获取后,进行校验,避免失效的链接被使用,造成错误

源码

// handle notFullTimeoutRetry
DruidPooledConnection poolableConnection;
try {
    // 核心逻辑
    System.out.println("getConnectionInternal");
    // 获取的池化数据库链接
    poolableConnection = getConnectionInternal(maxWaitMillis);
} catch (GetConnectionTimeoutException ex) {
    if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {
        notFullTimeoutRetryCnt++;
        if (LOG.isWarnEnabled()) {
            LOG.warn("get connection timeout retry : " + notFullTimeoutRetryCnt);
        }
        continue;
    }
    throw ex;
}

// testOnBorrow =false 每次获取链接都会验证
if (testOnBorrow) {
    // 发送一条validateSql SELECT 1
    boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
    if (!validate) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("skip not validate connection.");
        }

        // 如果不可用就回收
        discardConnection(poolableConnection.holder);
        continue;
    }
}

分析

申请链接时候会进行一次验证,比较消耗性能,我的理解是无论是开发环境还是生产环境都不需要打开,Druid 1.2.8版本已经有其他的属性去在获取链接时判断链接的有效性

testOnReturn

入口方法

当连接使用完,调用commit或者rollback方法后,连接池会回收该连接

// 回收链接时候
protected void recycle(DruidPooledConnection pooledConnection)

作用

确保归还的时候链接是有效的,如果无效则会丢弃,丢弃的过程中会用lock锁,造成其他线程的堵塞,

源码

if (testOnReturn) {
    boolean validate = testConnectionInternal(holder, physicalConnection);
    if (!validate) {
        JdbcUtils.close(physicalConnection);
    
        destroyCountUpdater.incrementAndGet(this);
    
        lock.lock();
        try {
            if (holder.active) {
                activeCount--;
                holder.active = false;
            }
            closeCount++;
        } finally {
            lock.unlock();
        }
        return;
    }
}

分析

每次事务提交或者回滚都会回收链接到连接池中,回收过程对链接判断是否有效,进行验证,浪费性能.

testWhileIdle

入口

与testOnBorrow一致,个人认为testWhileIdle就是代替testOnBorrow的

public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException

作用

如果每次从数据库连接池获取链接都进行验证,性能太差,所以引入了空闲时间,当某个链接获取时,快速的check其是否过期.当判断是过期的链接,则进行校验.

源码

if (testWhileIdle) {
    final DruidConnectionHolder holder = poolableConnection.holder;
    long currentTimeMillis = System.currentTimeMillis();
    // // 记录该链接最后活动时间
    long lastActiveTimeMillis = holder.lastActiveTimeMillis;
    // 记录该链接最后执行时间
    long lastExecTimeMillis = holder.lastExecTimeMillis;
    // 记录最后心跳时间
    long lastKeepTimeMillis = holder.lastKeepTimeMillis;

    // 是否检查执行时间 checkExecuteTime 默认false
    if (checkExecuteTime
            && lastExecTimeMillis != lastActiveTimeMillis) {
        lastActiveTimeMillis = lastExecTimeMillis;
    }

    // 如果最后一次心跳时间 大于 最后活动的时间,则赋值
    if (lastKeepTimeMillis > lastActiveTimeMillis) {
        lastActiveTimeMillis = lastKeepTimeMillis;
    }

    // 空闲时间 = 当前时间减去 - 最后活跃的时间
    long idleMillis = currentTimeMillis - lastActiveTimeMillis;

    // 默认60秒
    long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;

    if (timeBetweenEvictionRunsMillis <= 0) {
        timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
    }

    // 如果空闲时间 大于等于 60秒
    if (idleMillis >= timeBetweenEvictionRunsMillis || idleMillis < 0 ) {
        // 发送测试语句 SELECT 1 确保回收的是有效的链接
        boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
        if (!validate) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("skip not validate connection.");
            }

            // 回收
            discardConnection(poolableConnection.holder);
            continue;
        }
    }
}

分析

如果每次从数据库连接池获取链接都进行验证,性能太差,这就是原来testOnBorrow的做法.不可取.
所以引入了testWhileIdle,吸取了testOnBorrow的弊端,设计了保活机制,增加了校验的前置判断,对从数据库连接池中获取的链接先进行保活判断,不满足主观的保活判断,再客观判断(就是SELECT 1)

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容