Druid源码解析(四):获取连接(getPooledConnection)

  DruidDataSource连接池实现了javaX.sql包中DataSource接口的全部方法。getConnection也来自于javaX.sql.DataSource接口。 而DruidPooledConnection也实现了接口java.sql.Connection。 这样就能在各种场景中通过这个接口来获取数据库连接。

一、filter处理-责任链模式

  在执行getConnection方法的过程中,首先确认DataSource是否完成了初始化。由于 init方法采用了双重检测锁机制,如果初始化完成则不会再次执行,因此这里不会造成系统多次初始化。

  然后判断是否存在filter,如果存在,则使用经典的责任链模式。 new了一个FilterChainImpl对象,而这个对象的构造函数 this 。 查看了一下,DruidDataSource的父类DruidAbstractDataSource正好实现了DataSourceProxy接口,也就是说,DruidDataSource本身就是一个DataSourceProxy。 这样做的好处是,FilterChainImpl本身不用维护任何存放filters的数组,这个数组可以直接复用DruidDataSource中的数据结构。 在FilterChainImpl中:

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

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

        if (filters.size() > 0) {
            FilterChainImpl filterChain = new FilterChainImpl(this);
            return filterChain.dataSource_connect(this, maxWaitMillis);
        } else {
            return getConnectionDirect(maxWaitMillis);
        }
    }

   此时调用dataSource_connect时,判断pos和filter的数量,如果 pos 小于 filter数量,则使用责任链模式继续调用下一个。

    @Override
    public DruidPooledConnection dataSource_connect(DruidDataSource dataSource, long maxWaitMillis) throws SQLException {
        if (this.pos < filterSize) {
            DruidPooledConnection conn = nextFilter().dataSource_getConnection(this, dataSource, maxWaitMillis);
            return conn;
        }

        return dataSource.getConnectionDirect(maxWaitMillis);
    }

    private Filter nextFilter() {
        return getFilters()
                .get(pos++);
    }

 

  在调用到不满足条件的时候,则调用 dataSource.getConnectionDirect(maxWaitMillis); 直接创建一个连接之后返回,而实际上如果我们没有 filter 时,调用的也是这个方法。

二、getConnectionDirect

  getConnectionDirect方法也不是最终创建数据库连接的方法。 这个方法会通过一个for循环自旋,确保连接的创建。 在GetConnectionTimeoutException异常处理中,这个地方有一个重试次数notFullTimeoutRetryCount,每次重试的时间为maxWaitMillis。

for (;;) {
            // handle notFullTimeoutRetry
            DruidPooledConnection poolableConnection;
            try {
                //调用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,用在获取连接的时候执行validationQuery检测连接是否有效。这个配置会降低性能。

testOnReturn

默认值通常为false,用在归还连接的时候执行validationQuery检测连接是否有效,这个配置会降低性能。

testWhileIdle

这个值通常建议为true,连接空闲时间大于timeBetweenEvictionRunsMillis指定的毫秒,就会执行参数validationQuery指定的SQL来检测连接是否有效。这个参数会定期执行。

validationQuery

用来检测连接是否有效的sql,如果validationQuery为空,那么testOnBorrow、testOnReturn、testWhileIdle这三个参数都不会起作用,配置参考:validationQuery=SELECT 1

 

            //获取连接时检测
            if (testOnBorrow) {
                boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
                if (!validate) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("skip not validate connection.");
                    }

                    discardConnection(poolableConnection.holder);
                    continue;
                }
            } else {
                //检测连接是否关闭
                if (poolableConnection.conn.isClosed()) {
                    discardConnection(poolableConnection.holder); // 传入null,避免重复关闭
                    continue;
                }
                //空闲检测
                if (testWhileIdle) {
                    final DruidConnectionHolder holder = poolableConnection.holder;
                    long currentTimeMillis             = System.currentTimeMillis();
                    long lastActiveTimeMillis          = holder.lastActiveTimeMillis;
                    long lastExecTimeMillis            = holder.lastExecTimeMillis;
                    long lastKeepTimeMillis            = holder.lastKeepTimeMillis;

                    if (checkExecuteTime
                            && lastExecTimeMillis != lastActiveTimeMillis) {
                        lastActiveTimeMillis = lastExecTimeMillis;
                    }

                    if (lastKeepTimeMillis > lastActiveTimeMillis) {
                        lastActiveTimeMillis = lastKeepTimeMillis;
                    }

                    long idleMillis                    = currentTimeMillis - lastActiveTimeMillis;

                    long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;

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

                    if (idleMillis >= timeBetweenEvictionRunsMillis
                            || idleMillis < 0 // unexcepted branch
                            ) {
                        boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
                        if (!validate) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("skip not validate connection.");
                            }

                            discardConnection(poolableConnection.holder);
                             continue;
                        }
                    }
                }
            }

 

  上述检测过程都会调用testConnectionInternal(poolableConnection.holder, poolableConnection.conn);进行检测。

  此外还有一个很重要的参数removeAbandoned。这个参数相关的配置参数有:

参数

说明

removeAbandoned

如果连接泄露,是否需要回收泄露的连接,默认false;

logAbandoned

如果回收了泄露的连接,是否要打印一条log,默认false;

removeAbandonedTimeoutMillis

连接回收的超时时间,默认5分钟;

  参数removeAbandoned的作用在于,如果有线程从Druid中获取到了连接并没有及时归还,那么Druid就会定期检测该连接是否会处于运行状态,如果不处于运行状态,则被获取时间超过removeAbandonedTimeoutMillis就会强制回收该连接。 这个检测的过程是在回收线程中完成的,在getConnection的过程中,只是判断该参数是否被设置,然后加上对应的标识。

            if (removeAbandoned) {
                StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
                //设置 stackTrace
                poolableConnection.connectStackTrace = stackTrace;
                //设置setConnectedTimeNano
                poolableConnection.setConnectedTimeNano();
                //打开traceEnable
                poolableConnection.traceEnable = true;

                activeConnectionLock.lock();
                try {
                    activeConnections.put(poolableConnection, PRESENT);
                } finally {
                    activeConnectionLock.unlock();
                }
            }

 

  最后还需要对defaultAutoCommit参数进行处理:

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

 

  至此,一个getConnetion方法创建完毕。

三、getConnectionInternal

  getConnectionInternal方法中创建连接: 首先判断连接池状态 closed 和enable状态是否正确,如果不正确则抛出异常退出。

        if (closed) {
            connectErrorCountUpdater.incrementAndGet(this);
            throw new DataSourceClosedException("dataSource already closed at " + new Date(closeTimeMillis));
        }

        if (!enable) {
            connectErrorCountUpdater.incrementAndGet(this);

            if (disableException != null) {
                throw disableException;
            }

            throw new DataSourceDisableException();
        }

  

  之后的逻辑:

 for (boolean createDirect = false;;){
    if(createDirect){
        //直接创建连接的逻辑
    }
    
    if (maxWaitThreadCount > 0
        && notEmptyWaitThreadCount >= maxWaitThreadCount) {
        // 判断最大等待线程数如果大于0且notEmpty上的等待线程超过了这个值 那么抛出异常
        
        }

    //其他相关参数检测 抛出异常
    
    //判断如果createScheduler存在,且executor.getQueue().size()大于0 那么将启用createDirect逻辑,退出本持循环
    if (createScheduler != null
        && poolingCount == 0
        && activeCount < maxActive
        && creatingCountUpdater.get(this) == 0
        && createScheduler instanceof ScheduledThreadPoolExecutor) {
    ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) createScheduler;
    if (executor.getQueue().size() > 0) {
        createDirect = true;
        continue;
    }
    }
    
    //如果maxWait大于0,调用 pollLast(nanos),反之则调用takeLast()
    //获取连接的核心逻辑
    if (maxWait > 0) {
        holder = pollLast(nanos);
    } else {
        holder = takeLast();
    }

}

 

  getConnectionInternal 方法内部存在一大堆参数检测功能,根据一系列参数判断,是否需要直接创建一个连接。 反之,则调用pollLast 或 takeLast 方法。这两个方法如果获取不到连接,将会wait,之后通知生产者线程创建连接。 这个地方有一个风险就是,如果仅仅采用单线程的方式创建连接,一旦生产者线程由于其他原因阻塞,那么getConnection将会产被长时间阻塞。

之后获得holder之后,通过holder产生连接。

        holder.incrementUseCount();

        DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder);
        return poolalbeConnection;

 

四、pollLast 与 takeLast

  polllast的方法核心逻辑是自旋,判断是否有可用连接,然后发送empty消息,通知生产者线程可以创建连接。之后阻塞wait。只不过这个方法需要设置超时时间。  

for (;;) {
        //如果没有可用的连接
        if (poolingCount == 0) {
         emptySignal(); // send signal to CreateThread create connection

           estimate = notEmpty.awaitNanos(estimate); // signal by
           
         }
         //之后获取最后一个连接
           DruidConnectionHolder last = connections[poolingCount];
}       

 

  而takeLast方法与pollLast方法类似,只是等待的过程中,不增加超时时间,一直等到生产者的通知为止。
 while (poolingCount == 0) {
 
    emptySignal(); // send signal to CreateThread create connection
     
    try {
        notEmpty.await(); // signal by recycle or creator
    } finally {
        notEmptyWaitThreadCount--;
    }
    
 }
decrementPoolingCount();
//最后获取数组中的最后一个连接。
DruidConnectionHolder last = connections[poolingCount];
connections[poolingCount] = null;

 

posted @ 2022-05-14 00:56  李聪龙  阅读(2012)  评论(0编辑  收藏  举报