Druid源码阅读系列(七)

本文深入探讨了Druid连接池的核心方法shrink()和recycle()。shrink方法负责定期释放多余连接,而recycle方法在连接关闭时执行,决定连接是否能重新投入连接池。在多线程环境下,recycle方法确保了线程安全,避免了并发问题。文章还详细分析了recycle方法的执行流程,包括连接回滚、状态检查、连接池放回等步骤,以及涉及到的removeAbandoned属性和traceEnable调试功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

重点概括

其实Druid整个连接池最核心的两个方法是shrink()recycle(),它们两个的作用分别是shrink会对连接池进行瘦身,将多余的连接释放掉,shrink方法是在摧毁线程里会被周期性的调用,类似于一个定时任务。而recycle方法调用不是定时任务,而是在连接关闭的时候进行的,这里的的关闭不是实际上的连接关闭,而是调用的DruidPoolConnect.close()方法,具体实现其实就是先验证是否可以回收,可以的话扔进连接池,否则关闭,当然底层实现还有很多其他校验,如果感兴趣可以看我下面的源码分析。其实想想也很合理,连接一旦准备关闭,如果可能,放进连接池是最好的选择。

从程序设计的角度来讲,回收这个动作有close()这样一个很好的触发条件,但是瘦身动作并没有触发条件,所以设计为定时任务。

源码解析

我们前面已经将shrink()的源码相信分析了,这里就不在赘述,感兴趣的朋友可以回去看下Druid源码阅读系列(五),今天就说说recycle()方法。

recycle()

recycle()方法的入口是在DruidPooledConnectionclose()里。

@Override
public void close() throws SQLException {
    if (this.disable) {
        return;
    }

    DruidConnectionHolder holder = this.holder;
    if (holder == null) {
        if (dupCloseLogEnable) {
            LOG.error("dup close");
        }
        return;
    }

    DruidAbstractDataSource dataSource = holder.getDataSource();
    boolean isSameThread = this.getOwnerThread() == Thread.currentThread();

    if (!isSameThread) {
        dataSource.setAsyncCloseConnectionEnable(true);
    }

    if (dataSource.isAsyncCloseConnectionEnable()) {
        syncClose();
        return;
    }

    if (!CLOSING_UPDATER.compareAndSet(this, 0, 1)) {
        return;
    }

    try {
        for (ConnectionEventListener listener : holder.getConnectionEventListeners()) {
            listener.connectionClosed(new ConnectionEvent(this));
        }

        List<Filter> filters = dataSource.getProxyFilters();
        if (filters.size() > 0) {
            FilterChainImpl filterChain = new FilterChainImpl(dataSource);
            filterChain.dataSource_recycle(this);
        } else {
            recycle();
        }
    } finally {
        CLOSING_UPDATER.set(this, 0);
    }

    this.disable = true;
}

这里需要注意的是下面这块逻辑:

boolean isSameThread = this.getOwnerThread() == Thread.currentThread();

if (!isSameThread) {
    dataSource.setAsyncCloseConnectionEnable(true);
}

if (dataSource.isAsyncCloseConnectionEnable()) {
    syncClose();
    return;
}

这里会判断是否是同一个线程,如果不是同一个线程,不加锁可能造成同一时间一个连接被多个线程同时修改,引发错误,所以需要进行“同步”关闭。其实看“同步”的代码实现和非同步没啥区别,就是加了锁而已。

public void syncClose() throws SQLException {
    lock.lock();
    try {
        if (this.disable || CLOSING_UPDATER.get(this) != 0) {
            return;
        }

        DruidConnectionHolder holder = this.holder;
        if (holder == null) {
            if (dupCloseLogEnable) {
                LOG.error("dup close");
            }
            return;
        }

        if (!CLOSING_UPDATER.compareAndSet(this, 0, 1)) {
            return;
        }

        for (ConnectionEventListener listener : holder.getConnectionEventListeners()) {
            listener.connectionClosed(new ConnectionEvent(this));
        }

        DruidAbstractDataSource dataSource = holder.getDataSource();
        List<Filter> filters = dataSource.getProxyFilters();
        if (filters.size() > 0) {
            FilterChainImpl filterChain = new FilterChainImpl(dataSource);
            filterChain.dataSource_recycle(this);
        } else {
            recycle();
        }

        this.disable = true;
    } finally {
        CLOSING_UPDATER.set(this, 0);
        lock.unlock();
    }
}

这里需要注意的是即使是同一个线程,但是只要开启了removeAbandoned属性,也需要“同步”关闭,因为removeAbandoned是在其他线程里执行的,也会引发线程安全问题。removeAbandoned是将一个连接直接关闭而不经过连接池,recycle会直将可用连接放进连接池,如果不加锁,会出现一个已经关闭的连接却被放进连接池的情况。

public boolean isAsyncCloseConnectionEnable() {
    if (isRemoveAbandoned()) {
        return true;
    }
    return asyncCloseConnectionEnable;
}

recycle()方法如下:

public void recycle() throws SQLException {
    if (this.disable) {
        return;
    }

    DruidConnectionHolder holder = this.holder;
    if (holder == null) {
        if (dupCloseLogEnable) {
            LOG.error("dup close");
        }
        return;
    }

    if (!this.abandoned) {
        DruidAbstractDataSource dataSource = holder.getDataSource();
        dataSource.recycle(this);
    }

    this.holder = null;
    conn = null;
    transactionInfo = null;
    closed = true;
}

这里的abandoned属性是不是有点眼熟?对的,就是在前面章节里说的removedAvandoned方法里去设置的,当连接存活时间超过removeAbandonedTimeoutMillis时,会将这个连接直接close()并且打上abandoned标记。回收时会排除掉这些已经打上标记的无效连接。

下面是回收真正的核心代码:

    protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
        final DruidConnectionHolder holder = pooledConnection.holder;

        if (holder == null) {
            LOG.warn("connectionHolder is null");
            return;
        }

        //回收不同线程输出日志
        if (logDifferentThread //
            && (!isAsyncCloseConnectionEnable()) //
            && pooledConnection.ownerThread != Thread.currentThread()//
        ) {
            LOG.warn("get/close not same thread");
        }

        final Connection physicalConnection = holder.conn;

        //traceEnable搭配removeAbandoned一起使用,获取连接时当开启removeAbandoned之后,这个状态设置为true,
        // 当连接从activeConnections中取出或者connect disable的时候,设置为false.
        //这块是做removeAbandoned(连接泄露)相关调试
        if (pooledConnection.traceEnable) {
            Object oldInfo = null;
            activeConnectionLock.lock();
            try {
                if (pooledConnection.traceEnable) {
                    oldInfo = activeConnections.remove(pooledConnection);
                    pooledConnection.traceEnable = false;
                }
            } finally {
                activeConnectionLock.unlock();
            }
            if (oldInfo == null) {
                if (LOG.isWarnEnabled()) {
                    LOG.warn("remove abandonded failed. activeConnections.size " + activeConnections.size());
                }
            }
        }

        final boolean isAutoCommit = holder.underlyingAutoCommit;
        final boolean isReadOnly = holder.underlyingReadOnly;
        final boolean testOnReturn = this.testOnReturn;

        try {
            // check need to rollback?
            //自动提交或者只读同时不满足,说明代码需要回滚,执行回滚
            if ((!isAutoCommit) && (!isReadOnly)) {
                pooledConnection.rollback();
            }

            // reset holder, restore default settings, clear warnings
            boolean isSameThread = pooledConnection.ownerThread == Thread.currentThread();
            if (!isSameThread) {
                final ReentrantLock lock = pooledConnection.lock;
                lock.lock();
                try {
                    holder.reset();
                } finally {
                    lock.unlock();
                }
            } else {
                holder.reset();
            }

            //如果连接已经丢弃放弃回收
            if (holder.discard) {
                return;
            }

            //phyMaxUseCount这个是连接最大使用次数,针对分布式数据库优化的,通过达到一定使用次数后断开重连,使得多个服务器间负载更均衡。
            if (phyMaxUseCount > 0 && holder.useCount >= phyMaxUseCount) {
                discardConnection(holder);
                return;
            }

            //前面回滚操作如果发生异常,可能导致连接关闭,这个时候需要进行相应的数值刷新操作。
            if (physicalConnection.isClosed()) {
                lock.lock();
                try {
                    if (holder.active) {
                        activeCount--;
                        holder.active = false;
                    }
                    closeCount++;
                } finally {
                    lock.unlock();
                }
                return;
            }

            //testOnReturn设置为true,回收的时候会检测该连接是否有用,如果不可用会直接将连接关闭。
            if (testOnReturn) {
                boolean validate = testConnectionInternal(holder, physicalConnection);
                if (!validate) {
                    JdbcUtils.close(physicalConnection);

                    destroyCountUpdater.incrementAndGet(this);

                    lock.lock();
                    try {
                        if (holder.active) {
                            activeCount--;
                            holder.active = false;
                        }
                        closeCount++;
                    } finally {
                        lock.unlock();
                    }
                    return;
                }
            }
            if (holder.initSchema != null) {
                holder.conn.setSchema(holder.initSchema);
                holder.initSchema = null;
            }

            //这个指的是数据源是否可用
            if (!enable) {
                discardConnection(holder);
                return;
            }

            boolean result;
            final long currentTimeMillis = System.currentTimeMillis();

            //物理超时时间验证
            if (phyTimeoutMillis > 0) {
                long phyConnectTimeMillis = currentTimeMillis - holder.connectTimeMillis;
                if (phyConnectTimeMillis > phyTimeoutMillis) {
                    discardConnection(holder);
                    return;
                }
            }

            //这里开始将活跃连接放进连接池,putLast将后续连接放进连接池数组尾部,并进行相应计数,整个连接池其实有点像一个队列
            lock.lock();
            try {
                if (holder.active) {
                    activeCount--;
                    holder.active = false;
                }
                closeCount++;

                result = putLast(holder, currentTimeMillis);
                recycleCount++;
            } finally {
                lock.unlock();
            }

            //如果放入连接池失败,将连接关闭并输出日志
            if (!result) {
                JdbcUtils.close(holder.conn);
                LOG.info("connection recyle failed.");
            }
        } catch (Throwable e) {
            holder.clearStatementCache();

            if (!holder.discard) {
                discardConnection(holder);
                holder.discard = true;
            }

            LOG.error("recyle error", e);
            recycleErrorCountUpdater.incrementAndGet(this);
        }
    }

代码里面都写了注释,整体流程是先输出不同线程的日志(如果设置了的话),如果开启了removeAbandoned,将当前连接从activeConnections集合里面拿出来,并更新traceEnable状态,检查回滚执行条件,如何满足则执行回滚。不是同一个线程会重置holder的值。非discard的连接,验证phyMaxUseCount,连接使用次数超过则断开,这块主要是针对分布式数据库优化。前面回滚操作如果发生异常,可能导致连接关闭,需要进行相应的数值刷新操作。然后检查testOnReturn相关逻辑,接着是数据源有效性校验。物理超时验证,最后才将活跃连接放进连接池,putLast将后续连接放进连接池数组尾部,并进行相应计数,整个连接池其实有点像一个队列。如果放入失败则关闭连接打印日志。

这里强调一下这块逻辑:

if (physicalConnection.isClosed()) {
    lock.lock();
    try {
        if (holder.active) {
            activeCount--;
            holder.active = false;
        }
        closeCount++;
    } finally {
        lock.unlock();
    }
    return;
}

这块代码我其实想了很久,前面"所有"关闭连接的操作都被return了,那为啥还需要在去判断一下,一开始看着挺奇怪的,后面一步步往下看,看到回滚代码之后才发现里面有个catch块,那里面如果异常的话,可能会关闭连接,这时候会将isClosed设置为true,所以后面又这样一个判断。

疑问

今天看代码的时候有看到ConnectionEventListener这样一个接口,不太懂怎么用的,有时间去研究下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值