Druid源码解析(五):DruidDataSource的shrink过程(Druid source code analysis (V): the shrink process of druiddatasource)

  shrink方法是DestroyTask线程中回收连接的具体执行方法。

  首先获得锁:

try {
    lock.lockInterruptibly();
} catch (InterruptedException e) {
    return;
}

  之后,要判断初始化状态是否完成,如果采用异步初始化,可能DestoryTask线程已经启动,但是连接池还没有初始化完成。

if (!inited) {
    return;
}

  之后对连接池中的连接进行遍历,connections中,可连接的连接数记在poolingCount变量。 此时要记录一个checkCount,这个变量为 checkCount = poolingCount – minIdle;也就是checkCount为连接池中连接的数量减去最小空闲连接数设置minIdle。

  此后进入checkTime逻辑,checkTime是调用shrink传入的参数,通常DestroyTask的调用这个参数都为true。 此后check的参数有:

    判断物理连接是否超时:phyConnectTimeMillis > phyTimeoutMillis。如果超时,则将当前连接标记到evictConnections数组并退出当前循环。

    判断空闲时间是否超时: 如果空闲时间小于最小于配置的minEvictableIdleTimeMillis时间且同时小于配置的keepAliveBetweenTimeMillis(idleMillis < minEvictableIdleTimeMillis && idleMillis < keepAliveBetweenTimeMillis) 则结束循环。 反之,当idleMillis大于minEvictableIdleTimeMillis或者大于maxEvictableIdleTimeMillis都被标记到evictConnections数组。

    判断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 都需要通知生产者继续创建连接。

————————

The shrink method is the specific execution method of recycling connections in the destroytask thread.

Get the lock first:

try {
    lock.lockInterruptibly();
} catch (InterruptedException e) {
    return;
}

After that, it is necessary to judge whether the initialization status is completed. If asynchronous initialization is adopted, the destorytask thread may have been started, but the connection pool has not been initialized yet.

if (!inited) {
    return;
}

Then traverse the connections in the connection pool. In connections, the number of connections that can be connected is recorded in the poolingcount variable. At this time, a checkcount should be recorded. The variable is checkcount = poolingcount – minidle; That is, checkcount sets minidle for the number of connections in the connection pool minus the minimum number of free connections.

After that, enter the checktime logic. Checktime is the parameter passed in by calling shrink. Usually, this parameter is true in the call of destroytask. After that, the parameters of check are:

Determine whether the physical connection timed out: phyconnecttimemillis & gt; phyTimeoutMillis。 If it times out, mark the current connection to the evictconnections array and exit the current loop.

    判断空闲时间是否超时: 如果空闲时间小于最小于配置的minEvictableIdleTimeMillis时间且同时小于配置的keepAliveBetweenTimeMillis(idleMillis < minEvictableIdleTimeMillis && idleMillis < keepAliveBetweenTimeMillis) 则结束循环。 反之,当idleMillis大于minEvictableIdleTimeMillis或者大于maxEvictableIdleTimeMillis都被标记到evictConnections数组。

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

If checktime is false, all connections less than checkcount are marked to the evictconnections array.

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

After that, the removecount is processed. Removecount = evictcount + keepalivecount; The processing logic is as follows:

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;
}

This logic essentially removes the first n items calculated in connections. I haven’t understood this logic before. In fact, I need to take a detailed look at the logic in the for loop. In the for loop, if the checktime is false, all the previous checkcount connections will be removed directly. On the contrary, in connections, the returned connections are placed at the end of the connections array through the recycle method. The connections index can be obtained from the tail of the small connections. After the number of connections to be removed is determined here, you can directly remove the first checkcount connections of connections.

After removal, it can be unlocked. The removed connections are then processed.

} finally {
    lock.unlock();
}

For evict connections:

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);
}

Close the connection and empty evictconnections.

The keepalivecount connection needs to be discussed in several cases:

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);
}

For a connection in keepalive state, in order to better reuse the connection, first judge whether the connection is available. If it is available, call the put method to update the state of the connection and place it at the end of the connection pool. It can be seen that not all connections will be closed in shrink. For connections in keepalive status, you need to judge whether they are available. The available connections can also be reused.

At this time, there is another situation to consider, that is, the available connections are still not enough minidle, so the connection pool is not satisfied and you need to continue to create connections. This status is needfill:

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

Processing logic:

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();
    }
}

Both needfill and onfatalerror need to notify the producer to continue creating the connection.