com.alibaba.druid.pool.DruidDataSource源码解析

 

 

 

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<DruidPooledConnection> abandonedList = new ArrayList<DruidPooledConnection>();

    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完成的

猜你喜欢

转载自blog.csdn.net/lishoubin_198308/article/details/82772297