druid连接池源码分析

Druid
Druid是java语言中最好的数据库连接池之一,经常在项目中使用。本文基于目前最新的版本1.1.6,探索下Druid连接池的实现原理。因为涉及到各种复杂逻辑,暂未想到比较好的图示来描述源码,因此本文更多的是代码+文字,大家见谅哈

首先,提出几个问题,我们带着这几个疑问去学习druid连接池源码

druid常用的参数
druid如何创建连接的
connection是如何被回收利用的
druid如何维护、清理连接
druid怎么解决并发情况下获取、创建、关闭连接的问题
DruidDataSource基本原理
总体来说,DruidDataSource的原理很简单,就是ReentrantLock + Condition,经典的生产者/消费者模式的实际应用,伪代码如下

public class DruidDataSource {

// 默认使用公平锁
private ReentrantLock lock = new ReentrantLock(false);

private Condition notEmpty = lock.newCondition();

private Condition empty = lock.newCondition();

public Connection getConnection() {
    lock.lock;
    if ( "池中有连接" ) {
        // 从池中取出连接
    } else {
        empty.signal();     // 唤醒创建连接的线程进行创建连接
        notEmpty.await();   // 等创建连接的线程进行通知
    }
    lock.unlock();
}

// 调用Connection.close(),会调用DruidDataSource的recycle方法
public void recycle( Connection conn ) {
    // 对连接进行检测
    lock.lock;
    putLast();
    lock.unlock();
}

public CreateThread extends Thread {
    public void run() {
        for (;;) {
            lock.lockInterruptibly();
            if ( "池中有可用连接" ) {
                empty.await();      // 进入waiting,等等获取连接的线程唤醒
            } else {
                //TODO 创建连接
                notEmpty.signal();  // 唤醒等等连接的线程
            }
            lock.unlock();
        }
    }
}

public DestoryThread extends Thread {
    public void run() {
        for (;;) {
            lock.lockInterruptibly();
            // 清理连接池内的过期连接
            // (可选)清理从连接池拿走,正在使用的连接
            lock.unlock();
            Thread.sleep( times );
        }
    }
}

}

DruidDataSource内部结构
首先来看下继承关系,DruidDataSource继承至DruidAbstractDataSource(内部定义了各种参数和常量)

javax.naming.Referenceable:对JNDI支持
javax.management.MBeanRegistration:对jmx的支持,用于对内部数据进行jmx监控
com.alibaba.druid.pool.DruidDataSourceMBean:druid用于对DruidDataSource监控而抽象的接口
构造方法
构造方法可以指定DruidDataSource是否使用公平锁,默认使用公平锁,这个重入锁用于解决create、destory、get链接的并发问题。因此,如果使用公平锁,应用程序调用getConnection()是根据请求的先后顺序依次返回Connection。如果我们指定为非公平锁,则允许后来居上,后面到达的请求反而可能先获得Connection

ReentrantLock lock:创建Connection的线程、销毁的线程、获取连接的线程,需要获得重入锁,才可以对内部数据进行操作,当然只是在需要的地方加锁,后面会具体分析
Condition notEmpty:如果获取连接的线程发现连接池空了,一方面会唤醒empty,另外一方面自己会调用notEmpty.await()进入等待,由CreateConnectionThread唤醒,或者其他线程释放连接时唤醒
Condition empty:CreateConnectionThread只有在连接池不够用的情况下才会创建,否则调用empty.await()挂起线程。如果获取连接的线程发现连接全部被拿走了,则会调用empty.signal()唤醒CreateConnectionThread创建连接,同时也会调用notEmpty.await()进入等待
public DruidDataSource(){
this(false);
}

public DruidDataSource(boolean fairLock){
// 调用父类DruidAbstractDataSource,用于初始化Lock和Condition
super(fairLock);

// 使用系统参数对DruidDataSource进行设值
configFromPropety(System.getProperties());

}

public class DruidAbstractDataSource {

// other code...

protected ReentrantLock lock;
protected Condition notEmpty;
protected Condition empty;

public DruidAbstractDataSource(boolean lockFair){
    lock = new ReentrantLock(lockFair);
    notEmpty = lock.newCondition();
    empty = lock.newCondition();
}

}

初始化
每次获取Connection都会调用init,内部使用inited标识DataSource是否已经初始化OK,init主要完成以下工作:

  • 初始化Filter,这些Filter可以嵌入各个环节,包括创建、销毁链接,提交、回滚事务等等,比如常见的ConfigFilter(支持密码加密)、StatFilter(监控,比如打印慢查询SQL)、LogFilter(打印各种日志)。依次从wrapper jdbcUrl、setFilters指定的Filters、SPI加载Filter并进行初始化

加载数据库驱动Driver
根据不同的数据库,实例化ExceptionSorter,主要的api就是isExceptionFatal(SQLException e),用于判断是否是Fatal级别的异常
初始化连接检测器,不同数据库的实现不一样,比如mysql是调用pingInternal检测连接是否OK。ValidConnectionChecker在获取连接、回收连接的时候会用到
初始化JdbcDataSourceStat,主要目的是做监控
初始化connections、evictConnections、keepAliveConnections数组,分别用于存放可被获取的连接池、待清理的连接池、存活的连接池,数组的大小都是maxActive
初始化initialSize个Connection
开启LogStatsThread线程,用于定期打印DruidDataSource的一些数据,默认是不开启的,需要开启的话只需要设置timeBetweenLogStatsMillis指定打印的时间周期,log步骤需要获取主锁,建议时间不要设得太短
创建CreateConnectionThread线程,druid内部默认使用一个线程异步地创建连接,当然可以指定createScheduler线程池,开启多个线程创建连接,但是请把keepAlive设为true,否则不会开启异步线程创建连接
创建DestroyConnectionThread线程,定期扫描连接池内过期的连接,如果想对连接池外面正在使用的连接也进行清理的话,需要指定removeAbandoned为true,清理线程会判断连接是否正在使用,是否超过了清理时间而进行清理
初始化工作还是有点繁锁,但也比较关键吧。有些童鞋喜欢设置各种参数,需要注意下。比如期望开启多个创建连接的线程,可能只指定一个createScheduler线程池,然而可能并没有达到预期的效果,因为keepAlive默认是false的,不会启动多线程创建连接

public void init() throws SQLException {

// 由volatite修改,每次获取连接,也会调用init()
if (inited) {
    return;
}

// 可以被中断的lock操作
final ReentrantLock lock = this.lock;
try {
    lock.lockInterruptibly();
} catch (InterruptedException e) {
    throw new SQLException("interrupt", e);
}

boolean init = false;
try {
    if (inited) {
        return;
    }

    // 获取程序的调用栈,标注由哪个函数调用的init方法
    initStackTrace = Utils.toString(Thread.currentThread().getStackTrace());

    this.id = DruidDriver.createDataSourceId();
    if (this.id > 1) {
        long delta = (this.id - 1) * 100000;
        this.connectionIdSeed.addAndGet(delta);
        this.statementIdSeed.addAndGet(delta);
        this.resultSetIdSeed.addAndGet(delta);
        this.transactionIdSeed.addAndGet(delta);
    }

    // 这个地方用于支持wrapper jdbcUrl,可以使用filter=xxx,jmx=xxx这种方式,具体请参考官方文档
    // 比如使用jdbc:wrap-jdbc:filters=stat,log4j:jdbc:mysql:xxx这种方式可以配置Filter用于收集监控信息
    // 具体请参考源码 DruidDriver.parseConfig(jdbcUrl, null)
    if (this.jdbcUrl != null) {
        this.jdbcUrl = this.jdbcUrl.trim();
        initFromWrapDriverUrl();
    }

    // 初始化Filter
    for (Filter filter : filters) {
        filter.init(this);
    }

    // 各种参数校验,other code......  

    // 从SPI中加载Filter,如果前面加载的Filter不存在则还需要进行初始化,可以指定系统参数druid.load.spifilter.skip=false禁用该SPI
    initFromSPIServiceLoader();

    // 初始化Driver实例
    if (this.driver == null) {
        if (this.driverClass == null || this.driverClass.isEmpty()) {
            this.driverClass = JdbcUtils.getDriverClassName(this.jdbcUrl);
        }

        // 用于支持com.alibaba.druid.mock.MockDriver
        if (MockDriver.class.getName().equals(driverClass)) {
            driver = MockDriver.instance;
        } else {
            driver = JdbcUtils.createDriver(driverClassLoader, driverClass);
        }
    } else {
        if (this.driverClass == null) {
            this.driverClass = driver.getClass().getName();
        }
    }

    // 针对不同数据库的一些校验逻辑
    initCheck();

    // 初始化ExceptionSorter
    initExceptionSorter();

    // 初始化连接检测器,不同数据库的实现不一样,比如mysql是调用pingInternal检测连接是否OK
    initValidConnectionChecker();

    validationQueryCheck();

    // 初始化DataSource的监控器
    if (isUseGlobalDataSourceStat()) {
        dataSourceStat = JdbcDataSourceStat.getGlobal();
        if (dataSourceStat == null) {
            dataSourceStat = new JdbcDataSourceStat("Global", "Global", this.dbType);
            JdbcDataSourceStat.setGlobal(dataSourceStat);
        }
        if (dataSourceStat.getDbType() == null) {
            dataSourceStat.setDbType(this.dbType);
        }
    } else {
        dataSourceStat = new JdbcDataSourceStat(this.name, this.jdbcUrl, this.dbType, this.connectProperties);
    }
    dataSourceStat.setResetStatEnable(this.resetStatEnable);

    // 连接池中可用的连接(未被拿走),内部会维护一个poolingCount值代表队列中剩余可用的连接,每次从末尾拿走连接
    connections = new DruidConnectionHolder[maxActive];

    // 失效、过期的连接,会暂时放在这个数组里面
    evictConnections = new DruidConnectionHolder[maxActive];

    // 销毁线程会检测线程,如果检测存活的线程放暂时放在这里,然后统一放入connections中
    keepAliveConnections = new DruidConnectionHolder[maxActive];

    SQLException connectError = null;

    // 是否异常初始化连接池,如果不是的话,则初始化指定的initialSize个连接数
    boolean asyncInit = this.asyncInit && createScheduler == null;
    if (!asyncInit) {
        try {
            // init connections
            for (int i = 0, size = getInitialSize(); i < size; ++i) {
                PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();
                DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);
                connections[poolingCount] = holder;
                incrementPoolingCount();
            }

            if (poolingCount > 0) {
                poolingPeak = poolingCount;
                poolingPeakTime = System.currentTimeMillis();
            }
        } catch (SQLException ex) {
            LOG.error("init datasource error, url: " + this.getUrl(), ex);
            connectError = ex;
        }
    }

    /** 初始化必须的线程 */
    createAndLogThread();   // 开启logger日志打印的线程
    createAndStartCreatorThread();  // 开启创建连接的线程,如果线程池createScheduler为null,则开启单个创建连接的线程
    createAndStartDestroyThread();  // 开启销毁过期连接的线程

    // 等待前面的线程初始化完成
    initedLatch.await();
    init = true;

    initedTime = new Date();

    // 初始DataSource注册到jmx中
    registerMbean();

    if (connectError != null && poolingCount == 0) {
        throw connectError;
    }

    // keepAlive为true时,并且createScheduler不为null,则初始化minIdle个线程用于创建连接
    if (keepAlive) {
        // async fill to minIdle
        if (createScheduler != null) {
            for (int i = 0; i < minIdle; ++i) {
                createTaskCount++;
                CreateConnectionTask task = new CreateConnectionTask();
                this.createSchedulerFuture = createScheduler.submit(task);
            }
        } else {
            this.emptySignal();
        }
    }
} 
// catch各种异常,进行日志打印,并抛出异常
catch (Error e) {
    LOG.error("{dataSource-" + this.getID() + "} init error", e);
    throw e;
} 
finally {
    inited = true;
    lock.unlock();

    // 省略日志输出代码
}

}

获取连接
maxWait是获取连接时最长的等待时间,默认是-1,代表获取连接不会进行超时处理

public DruidPooledConnection getConnection() throws SQLException {
return getConnection(maxWait);
}

public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
init();

// 如果存在filter,则使用filter进行创建连接
if (filters.size() > 0) {
    FilterChainImpl filterChain = new FilterChainImpl(this);
    return filterChain.dataSource_connect(this, maxWaitMillis);
} else {
    return getConnectionDirect(maxWaitMillis);
}

}

分为以下几个动作:

调用getConnectionInternal获取经过各种包装的Connection,这个是获取连接的主要逻辑,支持超时时间,由DruidDataSource的maxWait参数指定,单位毫秒
如果testOnBorrow为true,则进行对连接进行校验,校验失败则进行清理并重新进入循环,否则跳到下一步
如果testWhileIdle为true,距离上次激活时间超过timeBetweenEvictionRunsMillis,则进行清理
如果removeAbandoned为true,则会把连接存放在activeConnections中,清理线程会对其定期进行处理
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
int notFullTimeoutRetryCnt = 0;

// 循环
for (;;) {
    // handle notFullTimeoutRetry
    DruidPooledConnection poolableConnection;
    try {
        poolableConnection = getConnectionInternal(maxWaitMillis);
    } catch (GetConnectionTimeoutException ex) {
        // logger error info
        throw ex;
    }

    // 如果testOnBorrow设为true的话,则在返回连接之前,需要进行校验,校验的逻辑不仅仅是判断是否被关闭,还需要调用ValidConnectionChecker进行check,前面的init里面也分析过
    // 需要注意的是,如果check失败,则会discard处理,并且重新走一遍循环
    if (testOnBorrow) {
        boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
        if (!validate) {
            Connection realConnection = poolableConnection.conn;
            discardConnection(realConnection);
            continue;
        }
    } else {
        Connection realConnection = poolableConnection.conn;
        if (poolableConnection.conn.isClosed()) {
            discardConnection(null); // 传入null,避免重复关闭
            continue;
        }

        // 如果testWhileIdle为true,并且上一次激活的时间如果超过清理线程执行的间距,则进行check动作
        if (testWhileIdle) {
            long currentTimeMillis             = System.currentTimeMillis();
            long lastActiveTimeMillis          = poolableConnection.holder.lastActiveTimeMillis;
            long idleMillis                    = currentTimeMillis - lastActiveTimeMillis;
            if (idleMillis >= timeBetweenEvictionRunsMillis
                    || idleMillis < 0 // unexcepted branch
                    ) {
                boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
                if (!validate) {
                    discardConnection(realConnection);
                    continue;
                }
            }
        }
    }

    // 如果removeAbandoned设为true,则把返回的线程保存起来,便于清理线程进行清理,注意只有removeAbandoned为true清理线程才会对池外的连接进行清理
    if (removeAbandoned) {
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        poolableConnection.connectStackTrace = stackTrace;
        poolableConnection.setConnectedTimeNano();
        poolableConnection.traceEnable = true;

        // 放在activeConnections中,它是一个IdentityHashMap,个人觉得完成可以使用并发的Map,可能是考虑hashCode的问题吧
        activeConnectionLock.lock();
        try {
            activeConnections.put(poolableConnection, PRESENT);
        } finally {
            activeConnectionLock.unlock();
        }
    }

    if (!this.defaultAutoCommit) {
        poolableConnection.setAutoCommit(false);
    }

    return poolableConnection;
}

}

getConnectionInternal
这一块没啥好分析的,主要是控制了创建连接的线程数量,以及处理异常,主要的逻辑由pollLast(nanos)或者takeLast()完成

private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {

// 省略部分代码
final long nanos = TimeUnit.MILLISECONDS.toNanos(maxWait);
final int maxWaitThreadCount = this.maxWaitThreadCount;

DruidConnectionHolder holder;

// createDirect只是针对createScheduler(创建连接的线程池)进行处理
for (boolean createDirect = false;;) {
    if (createDirect) {
        //TODO 忽略
    }

    // 对主锁加锁
    try {
        lock.lockInterruptibly();
    } catch (InterruptedException e) {
        connectErrorCountUpdater.incrementAndGet(this);
        throw new SQLException("interrupt", e);
    }

    try {
        // 如果等待创建连接的线程数如果大于maxWaitThreadCount,抛出异常,这个notEmptyWaitThreadCount是在pollLast(nanos)和takeLast()中设置
        if (maxWaitThreadCount > 0 && notEmptyWaitThreadCount >= maxWaitThreadCount) {
            connectErrorCountUpdater.incrementAndGet(this);
            throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
                    + lock.getQueueLength());
        }

        // 针对fatalError,抛出异常
        if (onFatalError
                && onFatalErrorMaxActive > 0
                && activeCount >= onFatalErrorMaxActive) {
            connectErrorCountUpdater.incrementAndGet(this);
            throw new SQLException(errorMsg, lastFatalError);
        }

        connectCount++;

        // 省略...... 针对存在createScheduler线程池的处理


        // 获取连接的核心逻辑,后续分析
        if (maxWait > 0) {
            holder = pollLast(nanos);
        } else {
            holder = takeLast();
        }

    } catch (InterruptedException e) {
        connectErrorCountUpdater.incrementAndGet(this);
        throw new SQLException(e.getMessage(), e);
    } catch (SQLException e) {
        connectErrorCountUpdater.incrementAndGet(this);
        throw e;
    } finally {
        lock.unlock();
    }

    break;
}

if (holder == null) {
    //TODO 抛出异常
}

holder.incrementUseCount();

// 包装下Connection
DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder);
return poolalbeConnection;

}

pollLast(nanos)
poll操作很简单,如果连接池内没有连接了,则调用empty.signal(),通知CreateThread创建连接,并且等待指定的时间,被唤醒之后再去查看是否有可用连接

private DruidConnectionHolder pollLast(long nanos) throws InterruptedException, SQLException {
long estimate = nanos;

for (;;) {
    if (poolingCount == 0) {

        // 唤醒CreateThread创建连接
        emptySignal(); // send signal to CreateThread create connection

        if (failFast && failContinuous.get()) {
            throw new DataSourceNotAvailableException(createError);
        }

        // 已经超时了
        if (estimate <= 0) {
            waitNanosLocal.set(nanos - estimate);
            return null;
        }

        notEmptyWaitThreadCount++;
        if (notEmptyWaitThreadCount > notEmptyWaitThreadPeak) {
            notEmptyWaitThreadPeak = notEmptyWaitThreadCount;
        }

        // 等待指定的时间
        try {
            long startEstimate = estimate;
            estimate = notEmpty.awaitNanos(estimate); // signal by recycle or creator
            notEmptyWaitCount++;
            notEmptyWaitNanos += (startEstimate - estimate);

            if (!enable) {
                connectErrorCountUpdater.incrementAndGet(this);
                throw new DataSourceDisableException();
            }
        } catch (InterruptedException ie) {
            notEmpty.signal(); // propagate to non-interrupted thread
            notEmptySignalCount++;
            throw ie;
        } finally {
            notEmptyWaitThreadCount--;
        }

        // 说明在等待时间内连接仍然未创建完成,返回null
        if (poolingCount == 0) {
            if (estimate > 0) {
                continue;
            }

            waitNanosLocal.set(nanos - estimate);
            return null;
        }
    }

    // poolingCount值减1,取出poolingCount索引的DruidConnectionHolder,并置为null
    decrementPoolingCount();
    DruidConnectionHolder last = connections[poolingCount];
    connections[poolingCount] = null;

    long waitNanos = nanos - estimate;
    last.setLastNotEmptyWaitNanos(waitNanos);

    return last;
}

}

/**

  • 如果不存在createScheduler,则唤醒创建线程

  • @return {[type]} [description]
    */
    private void emptySignal() {
    if (createScheduler == null) {
    empty.signal();
    return;
    }

    if (createTaskCount >= maxCreateTaskCount) {
    return;
    }

    if (activeCount + poolingCount + createTaskCount >= maxActive) {
    return;
    }

    createTaskCount++;
    CreateConnectionTask task = new CreateConnectionTask();
    this.createSchedulerFuture = createScheduler.submit(task);
    }

takeLast()
take操作和poll只是存在等待时间的差异,take会多次尝试获取连接,获取成功才会返回

异步创建Connection线程
CreateConnectionThread是一个守护线程,在需要时创建连接。在以下两个条件下进入await,获取连接的线程唤醒它才会创建物理连接:

连接池内的连接个数大于等待的线程数量,如果keepAlive为true的话,还需要考虑池外、池内的连接数,其中activeCount在返回连接时+1,回收链接时-1,也就是池外的连接数
如果池内、池外的连接数大于maxActive,也进入await
创建好物理连接之后,需要使用DruidConnectionHolder代理实际的物理连接,该对象持有DruidDataSource的引用,调用Connection最终会调用DruidDataSource的recyle(DruidPooledConnection conn)回收该连接,创建物理连接的过程是不加锁的,避免影响性能。

创建好连接之后,还需要把该连接put到连接池中,重新进行加锁。put过程是将连接存放在poolingCount索引,并且通知notEmpty取走连接,也就是需要获取连接的线程

public class CreateConnectionThread extends Thread {

public CreateConnectionThread(String name){
    super(name);
    this.setDaemon(true);
}

public void run() {
    initedLatch.countDown();

    long lastDiscardCount = 0;
    int errorCount = 0;
    for (;;) {
        // addLast
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e2) {
            break;
        }

        // 省略部分代码......
        try {
            boolean emptyWait = true;

            // 省略部分代码......

            if (emptyWait) {
                // 必须存在线程等待,才创建连接
                if (poolingCount >= notEmptyWaitThreadCount //
                        && !(keepAlive && activeCount + poolingCount < minIdle)) {
                    empty.await();
                }

                // 防止创建超过maxActive数量的连接,需要把池外的连接考虑进来
                if (activeCount + poolingCount >= maxActive) {
                    empty.await();
                    continue;
                }
            }

        } catch (InterruptedException e) {
            // 异常处理,忽略
            break;
        } finally {
            lock.unlock();
        }

        PhysicalConnectionInfo connection = null;

        try {
            connection = createPhysicalConnection();
            setFailContinuous(false);
        } catch (SQLException e) {
            // 异常处理,忽略......
        }

        if (connection == null) {
            continue;
        }

        boolean result = put(connection);
        errorCount = 0; // reset errorCount
    }
}

}

protected boolean put(PhysicalConnectionInfo physicalConnectionInfo) {
DruidConnectionHolder holder = null;
try {
holder = new DruidConnectionHolder(DruidDataSource.this, physicalConnectionInfo);
} catch (SQLException ex) {
// 异常处理
return false;
}
return put(holder);
}

private boolean put(DruidConnectionHolder holder) {
lock.lock();
try {
// 再次校验,避免创建的连接超过maxActive
if (poolingCount >= maxActive) {
return false;
}

    // 放回连接池中
    connections[poolingCount] = holder;
    incrementPoolingCount();

    if (poolingCount > poolingPeak) {
        poolingPeak = poolingCount;
        poolingPeakTime = System.currentTimeMillis();
    }

    // 通知获取连接的线程来取走
    notEmpty.signal();
    notEmptySignalCount++;

    if (createScheduler != null) {
        // ......
    }
} finally {
    lock.unlock();
}
return true;

}

Connection回收
在前面也提到了线程的回收,当我们应用程序调用Connection#close(),实际上调用的是DruidDataSource的recyle(DruidPooledConnection conn),我们直接分析recyle的实现

先是做一些检测工作,比如是否被关闭
根据testOnReturn参数校验连接的可用性,主要是调用testConnectionInternal函数,前面也提到过
把连接放回连接池,需要加锁处理,也是把连接放到poolingCount位置,并且通知notEmpty来消费连接
protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {

// testOnReturn为true的话,则会校验链接的可用性,和testOnBorrow类似
if (testOnReturn) {
    boolean validate = testConnectionInternal(holder, physicalConnection);
    if (!validate) {
        // 失效处理...
        return;
    }
}

// 放在连接池的末尾
lock.lock();
try {
    activeCount--;
    closeCount++;
    result = putLast(holder, lastActiveTimeMillis);
    recycleCount++;
} finally {
    lock.unlock();
}

}

boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
if (poolingCount >= maxActive) {
return false;
}

e.lastActiveTimeMillis = lastActiveTimeMillis;
connections[poolingCount] = e;
incrementPoolingCount();

if (poolingCount > poolingPeak) {
    poolingPeak = poolingCount;
    poolingPeakTime = lastActiveTimeMillis;
}

notEmpty.signal();
notEmptySignalCount++;

return true;

}

异步清理Connection
DestroyConnectionThread线程会定期执行一次清理动作,默认是60000ms执行一次,可以指定timeBetweenEvictionRunsMillis控制清理的频率,主要逻辑在于DestroyTask,首先会执行shrink对过期时间进行处理,然后根据removeAbandoned的值判断是否需要进行清理abandoned的连接。shrink只针对连接池的连接进行清理,而removeAbandoned会对从连接池外的连接进行清理

public class DestroyConnectionThread extends Thread {
public DestroyConnectionThread(String name){
super(name);
this.setDaemon(true);
}
public void run() {
initedLatch.countDown();

    // 忽略异常等细节
    for (;;) {
        if (timeBetweenEvictionRunsMillis > 0) {
            Thread.sleep(timeBetweenEvictionRunsMillis);
        } else {
            Thread.sleep(1000); //
        }

        if (Thread.interrupted()) {
            break;
        }

        destroyTask.run();
    }
}

}

public class DestroyTask implements Runnable {
public void run() {
shrink(true, keepAlive);
if (isRemoveAbandoned()) {
removeAbandoned();
}
}

}

shrink
shrink只会清理连接池内的连接,有几个参数,需要注意下:

phyTimeoutMillis:连接最大存活时间,默认是-1(不限制物理连接时间),从创建连接开始计算,如果超过该时间,则会被清理
keepAlive:默认是false,标记连接池内的连接是否需要保持存活,如果设为true的话,则会每次清理的时候,都会调用validateConnection,针对mysql而言就会往db发ping维持连接;这和前面提到的testOnBorrow、testWhileIdle类似
lastActiveTimeMillis:上一次激活的时间,在连接被recycle,或者清理线程在清理的时候会重新标记该时间(需要指定kepAlive为true)
minEvictableIdleTimeMillis:默认是1800000ms,如果上次激活时间lastActiveTimeMillis至当前时刻,如果小于该时间,则不进行清理
maxEvictableIdleTimeMillis:默认是25200000ms,和上面的minEvictableIdleTimeMillis类似,如果超过了maxEvictableIdleTimeMillis,则该连接会被清理掉
shrink对时间的处理比较复杂,下面基于checkTime为true的情况进行分析

如果是指定了phyTimeoutMillis,并且创建时间大于该值,则直接回收
如果上次激活时间小于minEvictableIdleTimeMillis值则不进行处理
如果上次激活时间小于minEvictableIdleTimeMillis,则清理掉checkCount个连接(需要维持在minIdle个连接)
如果超过了maxEvictableIdleTimeMillis直接清理
如果介于minEvictableIdleTimeMillis和maxEvictableIdleTimeMillis之间,并且keepAlive是true,则需要keepAlive
基于上面的规则,然后对connections连接池数组进行copy,清理掉无效的连接以及需要keepAlive的连接;然后关闭失效的连接,针对keepAlive的连接还需要校验连接,如果校验失败则直接关闭,校验成功则更新其lastActiveTimeMillis时间,并重新放入连接池中

public void shrink(boolean checkTime, boolean keepAlive) {
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
return;
}

int evictCount = 0;
int keepAliveCount = 0;
try {
    if (!inited) {
        return;
    }

    // checkCount代表超过minIdle的连接数
    final int checkCount = poolingCount - minIdle;
    final long currentTimeMillis = System.currentTimeMillis();

    // 清理连接池内的连接
    for (int i = 0; i < poolingCount; ++i) {
        DruidConnectionHolder connection = connections[i];

        if (checkTime) {

            // 检测物理连接时间限制,从创建时间作为起始点
            if (phyTimeoutMillis > 0) {
                long phyConnectTimeMillis = currentTimeMillis - connection.connectTimeMillis;
                if (phyConnectTimeMillis > phyTimeoutMillis) {
                    evictConnections[evictCount++] = connection;
                    continue;
                }
            }

            // 上次激活时间小于minEvictableIdleTimeMillis值则不管它
            long idleMillis = currentTimeMillis - connection.lastActiveTimeMillis;
            if (idleMillis < minEvictableIdleTimeMillis) {
                break;
            }

            // 咦,明明这个checkTime是true,如果进入了上面的if,可能是笔误吧
            // 如果上次激活时间小于minEvictableIdleTimeMillis,则清理掉checkCount个连接(需要维持在minIdle个连接)
            if (checkTime && i < checkCount) {
                evictConnections[evictCount++] = connection;
            } 
            // 超过了maxEvictableIdleTimeMillis直接清理
            else if (idleMillis > maxEvictableIdleTimeMillis) {
                evictConnections[evictCount++] = connection;
            } 
            // 介于minEvictableIdleTimeMillis和maxEvictableIdleTimeMillis之间,并且keepAlive是true,则需要keepAlive
            else if (keepAlive) {
                keepAliveConnections[keepAliveCount++] = connection;
            }
        } else {
            if (i < checkCount) {
                evictConnections[evictCount++] = connection;
            } else {
                break;
            }
        }
    }

    // 把不需要的连接干掉,包括keepAlive的连接,keepAlive的连接在下面会重新put到连接池中
    int removeCount = evictCount + keepAliveCount;
    if (removeCount > 0) {
        System.arraycopy(connections, removeCount, connections, 0, poolingCount - removeCount);
        Arrays.fill(connections, poolingCount - removeCount, poolingCount, null);
        poolingCount -= removeCount;
    }
    keepAliveCheckCount += keepAliveCount;
} finally {
    lock.unlock();
}

// 关闭失效的连接
if (evictCount > 0) {
    for (int i = 0; i < evictCount; ++i) {
        DruidConnectionHolder item = evictConnections[i];
        Connection connection = item.getConnection();
        JdbcUtils.close(connection);
        destroyCount.incrementAndGet();
    }
    Arrays.fill(evictConnections, null);
}

// 处理需要保持存活的连接,首先要调用validateConnection校验是否有效
// 如果有效,则更新其lastActiveTimeMillis时间,并且放回连接池中
// 如果失效,则直接关闭连接
if (keepAliveCount > 0) {
    this.getDataSourceStat().addKeepAliveCheckCount(keepAliveCount);
    // keep order
    for (int i = keepAliveCount - 1; i >= 0; --i) {
        DruidConnectionHolder holer = keepAliveConnections[i];
        Connection connection = holer.getConnection();
        holer.incrementKeepAliveCheckCount();

        boolean validate = false;
        try {
            this.validateConnection(connection);
            validate = true;
        } catch (Throwable error) {
           // logger
        }

        if (validate) {
            holer.lastActiveTimeMillis = System.currentTimeMillis();
            put(holer);
        } else {
            JdbcUtils.close(connection);
        }
    }

    // 清空数据,避免内存泄露
    Arrays.fill(keepAliveConnections, null);
}

}

removeAbandoned
针对连接池外的连接进行清理,可以避免数据库死锁导致连接无法被释放的问题,这个应该是设计的初衷吧。

如果Connection仍然处在running状态则不进行处理,但是个人有个疑问,比如sql执行过程中数据库发生死锁了,那么这个running值应该为true,也不会被处理
如果连接时间超过了removeAbandonedTimeoutMillis值,则进行后续的废弃操作
废弃操作会判断连接是否disable的,是的话则会关闭连接,否则不进行处理
public int removeAbandoned() {
int removeCount = 0;
long currrentNanos = System.nanoTime();
List abandonedList = new ArrayList();

activeConnectionLock.lock();
try {
    Iterator<DruidPooledConnection> iter = activeConnections.keySet().iterator();

    for (; iter.hasNext();) {
        DruidPooledConnection pooledConnection = iter.next();

        // 判断是否在running,这个是由Filter为其设置值
        if (pooledConnection.isRunning()) {
            continue;
        }

        // 如果连接时间超过了removeAbandonedTimeoutMillis值,则进行后续的废弃操作
        long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000);
        if (timeMillis >= removeAbandonedTimeoutMillis) {
            iter.remove();
            pooledConnection.setTraceEnable(false);
            abandonedList.add(pooledConnection);
        }
    }
} finally {
    activeConnectionLock.unlock();
}

// 如果连接是disable的,则会关闭连接,否则不进行处理
if (abandonedList.size() > 0) {
    for (DruidPooledConnection pooledConnection : abandonedList) {
        final ReentrantLock lock = pooledConnection.lock;
        lock.lock();
        try {
            if (pooledConnection.isDisable()) {
                continue;
            }
        } finally {
            lock.unlock();
        }

        JdbcUtils.close(pooledConnection);
        pooledConnection.abandond();
        removeAbandonedCount++;
        removeCount++;

        if (isLogAbandoned()) {
            // logger
        }
    }
}

return removeCount;

}

总结
整个DruidDataSource的实现并不复杂,但是各种参数,各种逻辑的处理比较繁锁,但是通过阅读源码,我们可以更好的理解Druid,比如设计原理、参数优化等等,而不是随意调整设置参数
DruidDataSource用到了重入锁来控制读写的并发问题,但是很多地方尽量将锁细粒度化,在需要的那一小段逻辑内才会使用
Filter使用了责任链模式,这使得druid的扩展性很强,比如druid的密码加密处理、监控数据的采集,都是通过Filter完成的

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值