Druid source code reading 5-DruidDataSource shrink process

This article has participated in the "Newcomer Creation Ceremony" activity, and started the road of Nuggets creation together

The shrink method is the specific execution method for recycling connections in the DestroyTask thread.

First acquire the lock:

try {
    lock.lockInterruptibly();
} catch (InterruptedException e) {
    return;
}
复制代码

After that, it is necessary to judge whether the initialization state is completed. If asynchronous initialization is used, the DestoryTask thread may have been started, but the connection pool has not been initialized.

if (!inited) {
    return;
}
复制代码

After that, the connections in the connection pool are traversed. In the connections, the number of connections that can be connected is recorded in the poolingCount variable. At this time, a checkCount should be recorded. This variable is checkCount = poolingCount - minIdle; that is, checkCount is the number of connections in the connection pool minus the minimum number of idle connections to set minIdle.

Then enter the checkTime logic, checkTime is the parameter passed in when calling shrink, usually this parameter is true when calling DestroyTask. After that, the parameters of check are:

  • Determine whether the physical connection has timed out: phyConnectTimeMillis > phyTimeoutMillis. If it times out, mark the current connection to the evictConnections array and exit the current loop.
  • Determine whether the idle time has expired:

If the idle time is less than the configured minEvictableIdleTimeMillis time and less than the configured keepAliveBetweenTimeMillis(idleMillis < minEvictableIdleTimeMillis && idleMillis < keepAliveBetweenTimeMillis), the loop ends. Conversely, when idleMillis is greater than minEvictableIdleTimeMillis or greater than maxEvictableIdleTimeMillis, it is marked in the evictConnections array.

  • 判断keepAlive是否超时:如果idleMillis >= keepAliveBetweenTimeMillis,则标记到keepAliveConnections数组。

如果checkTime为false,则将小于checkCount的全部连接都标记到evictConnections数组。

if (i < checkCount) {
    evictConnections[evictCount++] = connection;
} else {
    break;
}
复制代码

这之后进行removeCount的处理,removeCount = evictCount + keepAliveCount; 处理逻辑如下:

if (removeCount > 0) {
    //将connections从removeCount到poolingCount的连接向前移动poolingCount - removeCount。
    System.arraycopy(connections, removeCount, connections, 0, poolingCount - removeCount);
    //将poolingCount - removeCount后续部分都置为空。
    Arrays.fill(connections, poolingCount - removeCount, poolingCount, null);
    poolingCount -= removeCount;
}
复制代码

这个逻辑实质上是将connections中计算出来的前N项都移除。 之前一直不理解这个逻辑,实际上需要详细看一下for循环中的逻辑。for循环中,如果checkTime为false,则直接将前面checkCount个连接都移除。 反之,由于connections中,通过recycle方法,将放回的连接都放在connections数组的最后面。get的连接也是从connections的尾部获取,那么可以确保connections的连接,index小的连接最少被使用。 那么在这里确定了需要移除的连接数之后,直接就可以将connetions的前面checkCount个连接都移除。

移除之后,可以解锁。之后对移除的连接进行处理。

} finally {
    lock.unlock();
}
复制代码

对于evict的连接:

if (evictCount > 0) {
    for (int i = 0; i < evictCount; ++i) {
        DruidConnectionHolder item = evictConnections[i];
        Connection connection = item.getConnection();
        //关闭连接
        JdbcUtils.close(connection);
        //更新计数器
        destroyCountUpdater.incrementAndGet(this);
    }
    //将evictConnections清空
    Arrays.fill(evictConnections, null);
}
复制代码

关闭连接并清空evictConnections。

对于keepAliveCount连接,则需要分几种情况进行讨论:

if (keepAliveCount > 0) {
    // 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) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("keepAliveErr", error);
            }
            // skip
        }

        boolean discard = !validate;
        //如果可用,则直接put到connections中,放置到尾部。
        if (validate) {
            holer.lastKeepTimeMillis = System.currentTimeMillis();
            boolean putOk = put(holer, 0L, true);
            if (!putOk) {
                discard = true;
            }
        }
		
		//如果不可用,则关闭连接
        if (discard) {
            try {
                connection.close();
            } catch (Exception e) {
                // skip
            }

            lock.lock();
            //加锁更新计数器
            try {
                discardCount++;

                if (activeCount + poolingCount <= minIdle) {
                    emptySignal();
                }
            } finally {
                lock.unlock();
            }
        }
    }
    this.getDataSourceStat().addKeepAliveCheckCount(keepAliveCount);
    Arrays.fill(keepAliveConnections, null);
}
复制代码

对于keepalive状态的连接,为了更好的复用该连接,则首先判断该连接是否可用,如果可用,则调用put方法,将该连接的状态更新之后,放置到连接池的尾部。 可见,shrink中,并非所有的连接都会关闭,对于keepalive状态的连接,需要判断是否可用。可用的连接还可再次复用。

此时还有一种情况需要考虑,就是此时可用的连接仍然不够minIdle,那么连接池不满,需要继续创建连接。这个状态为needFill:

//keepAlive状态,且连接池中的连接加上被使用的连接仍然小于minIdle
if (keepAlive && poolingCount + activeCount < minIdle) {
    needFill = true;
}
复制代码

处理逻辑:

if (needFill) {
//加锁
    lock.lock();
    try {
        //如果minIdle 减去activeCount + poolingCount + createTaskCount 仍然不满,则通知创建线程创建连接
        int fillCount = minIdle - (activeCount + poolingCount + createTaskCount);
        for (int i = 0; i < fillCount; ++i) {
            emptySignal();
        }
        //解锁
    } finally {
        lock.unlock();
    }
} else if (onFatalError || fatalErrorIncrement > 0) {
    lock.lock();
    try {
        emptySignal();
    } finally {
        lock.unlock();
    }
}
复制代码

needFill和onFatalError 都需要通知生产者继续创建连接。

至此,shrink方法分析完毕。

Guess you like

Origin juejin.im/post/7078495292407939080