连接池 Druid (四) - 连接归还

2023-12-06 03:36
文章标签 连接 连接池 druid 归还

本文主要是介绍连接池 Druid (四) - 连接归还,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

轻车熟路,连接归还是通过Connection的代理对象重写close方法完成的,通过前面的学习我们已经知道Connectin的代理对象是DruidPooledConnection,所以我们直接看DruidPooledConnection的close方法。

DruidPooledConnection#close

直接上代码:

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

判断当前Connection(DruidPooledConnection)的状态为disbale、或者connectionHolder(DruidConnectionHolder)为null(说明连接已关闭了),则直接返回。

然后判断连接的ownerThread(获取connection的线程)与当前线程不是同一线程的话,则设置异步关闭asyncCloseConnectionEnable=true。调用syncClose()方法。

syncClose()方法和同线程关闭方式的代码逻辑基本一致,只不过syncClose()整个方法需要加锁,同线程关闭则不需要。

所以我们就不贴出syncClose()方法的源码了。

在什么场景下数据库连接会跨线程关闭?一个线程获取到数据库连接,然后会交给另外一个线程,由另外一个线程执行连接关闭?应用层这么做是为了实现多线程之间的事务处理?

接下来:

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

CAS方式修改当前对象的closing属性值为1(0->1),如果修改不成功,则说明有其他线程正在试图关闭当前连接,直接返回。

获取ConnectionHolder的所有ConnectionEventListener对象,给所有监听对象发送连接关闭通知。

然后,获取dataSource的ProxyFilters,不空的话调用filterChain的dataSource_recycle方法,有关dataSource过滤器我们依然暂时不管。没有filter的话,调用recycle()回收连接,之后CAS方法修改closing属性值为0,并设置当前连接对象的disable=true。

所以,我们发现连接归还应该是recycle()方法中实现的。

DruidPooledConnection#recycle()

连接(DruidPooledConnection)的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)的话,调用dataSource的recycle方法回收,之后清理相关对象、设置close为true。

abandoned是在removeAbandoned参数打开、连接执行时长超过设定的超时时长removeAbandonedTimeoutMillis之后设置的。有关removeAbandoned我们后面会专门进行分析,此处略过。

那接下来就是DataSource的recycle方法了。

DruidDataSrouce#recycle

代码比较长,我们还是分段分析:

/*** 回收连接*/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;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());}}}

吐槽一句,终于出现了哪怕是一句话的javaDoc说明:回收连接。整个Druid连接池的源码中,都非常吝啬,少有注释。

判断当前连接DruidPooledConnection的traceEnable属性为true的话,加activeConnectionLock锁之后,将当前连接从activeConnections中移除。

traceEnable属性其实反应的还是removeAbandoned参数(这个参数虽然在正式环境不建议打开,但是代码中阴魂不散,到处都有)。removeAbandoned参数打开的情况下,获取连接的时候会将连接放入activeConnections中,并同时设置连接的traceEnable为true。所以我们其实可以认为这个traceEnable其实就是removeAbandoned参数,换了个名字而已。

所以这里的逻辑是:removeAbandoned参数打开的话,连接归还的时候如果该连接没有被abandoned的话,在归还连接时会将当前连接从activeConnections中移除,该连接就会避免被abandoned处理。

接下来:

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

对没有提交的事务做回滚处理。之后调用DruidConnectionHolder的reset方法,重新设置连接属性为默认值,因为连接获取之后应用从根据需要可能对连接属性重新进行了设置,归还的时候重新设置会默认值,以便每一次应用获取连接之后都能拿到各项属性设置为默认值的连接、而不是不确定。

这里对没有提交的事务的判断条件是!isAutoCommit,获取的是物理连接Connection的isAutocommit属性。我们知道如果应用开启事务管理的话,获取连接之后会设置连接的autoCommit为false,事务提交或回滚之后,一般情况下应用也会恢复连接的默认设置,这个时候autoCommit就会被恢复为true。比如Spring的事务框架在事务提交之后会通过TransactionManager的cleanupAfterCompletion->doCleanupAfterCompletion设置autoCommit为true。所以正常来讲,应用已经提交事务之后,autoCommit就会变为true,也就不需要Druid在归还连接的时候处理回滚了。

Druid增加这个判断可能是为了给应用擦屁股(猜测而已),应用层没有启用事务框架的情况下手动开启了事务、设置autoCommit为false,执行完成之后没有重置autoCommit,这个时候应用可能commit、也可能没有commit事务,Durid的处理方式是:一律回滚。

仔细检查了HikariPool的连接回收过程,并没有这个回滚处理。个人不赞成(基于猜测而已………)Druid的这个回滚处理,因为事务提交还是回滚终究还是应用应该关注的事情,应用层为了简化处理逻辑可以把事务管理交给框架,比如Spring事务框架来处理。也就是说,要么是应用层自己解决、要么交给事务框架解决事务的提交或回滚是比较合理的选择。

继续。

检查如果DruidConnectionHolder已经被弃用(连接已经被弃用),直接返回,不归还。

检查如果物理连接已经被关闭,则加锁修改当前holder的active状态为false,直接返回,不归还。

然后:

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

检查testOnReturn参数如果设置为true,则测试连接可用性,如果连接不可用,处理方式同上,修改holder的active=false,直接返回,不归还。

testOnReturn参数也没有必要设置,道理和testOnBorrow参数一样,会影响性能。这两个参数的默认设置都是false,不打开。

之后检查当前dataSrouce如果不可用,则调用discardConnection关闭连接,不归还。

然后:

            boolean result;final long currentTimeMillis = System.currentTimeMillis();if (phyTimeoutMillis > 0) {long phyConnectTimeMillis = currentTimeMillis - holder.connectTimeMillis;if (phyConnectTimeMillis > phyTimeoutMillis) {discardConnection(holder);return;}}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);}}

如果设置了phyTimeoutMillis(默认设置为-1,不检查)的话,检查当前连接创建以来的时长是否超过了该参数的设置,超过的话关闭连接,不归还。这个参数也不建议设置。

之后,加锁,调用putLast(holder, currentTimeMillis);归还连接,如果归还失败,则调用JdbcUtil.close方法,底层直接关闭连接。

putLast是归还连接的核心方法。

DruidDataSource#putLast

putLast是连接归还的最后一步,目的是各种检查校验、连接属性重置之后,最终将连接放回到连接池connections中。

putLast是在锁状态下执行的。

看代码:

   boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {if (poolingCount >= maxActive || e.discard || this.closed) {return false;}e.lastActiveTimeMillis = lastActiveTimeMillis;connections[poolingCount] = e;incrementPoolingCount();if (poolingCount > poolingPeak) {poolingPeak = poolingCount;poolingPeakTime = lastActiveTimeMillis;}notEmpty.signal();notEmptySignalCount++;return true;}

检查如果没必要归还的话,比如连接池数量大于参数设定的maxActive数量、或者当前连接已经被遗弃、或者当前dataSource已经被关闭,则不需要归还,直接返回。

将连接放入到connections的尾部,之后增加连接池数量poolingCount。

调用notEmpty.signal();唤醒等待获取连接的线程:连接池中有新的连接加入,可以获取了。

Druid关闭连接(归还连接)代码分析完毕!

小结

Druid连接池的初始化、连接获取以及连接归还的源码分析完毕,后面会补充一篇文章分析连接遗弃的处理。

Thanks a lot!

上一篇 连接池 Druid (三) - 获取连接 getConnection
下一篇 连接池 Druid (补充) - removeAbandoned

这篇关于连接池 Druid (四) - 连接归还的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/460271

相关文章

Spring Boot 整合 MyBatis 连接数据库及常见问题

《SpringBoot整合MyBatis连接数据库及常见问题》MyBatis是一个优秀的持久层框架,支持定制化SQL、存储过程以及高级映射,下面详细介绍如何在SpringBoot项目中整合My... 目录一、基本配置1. 添加依赖2. 配置数据库连接二、项目结构三、核心组件实现(示例)1. 实体类2. Ma

电脑win32spl.dll文件丢失咋办? win32spl.dll丢失无法连接打印机修复技巧

《电脑win32spl.dll文件丢失咋办?win32spl.dll丢失无法连接打印机修复技巧》电脑突然提示win32spl.dll文件丢失,打印机死活连不上,今天就来给大家详细讲解一下这个问题的解... 不知道大家在使用电脑的时候是否遇到过关于win32spl.dll文件丢失的问题,win32spl.dl

Windows Server服务器上配置FileZilla后,FTP连接不上?

《WindowsServer服务器上配置FileZilla后,FTP连接不上?》WindowsServer服务器上配置FileZilla后,FTP连接错误和操作超时的问题,应该如何解决?首先,通过... 目录在Windohttp://www.chinasem.cnws防火墙开启的情况下,遇到的错误如下:无法与

IDEA连接达梦数据库的详细配置指南

《IDEA连接达梦数据库的详细配置指南》达梦数据库(DMDatabase)作为国产关系型数据库的代表,广泛应用于企业级系统开发,本文将详细介绍如何在IntelliJIDEA中配置并连接达梦数据库,助力... 目录准备工作1. 下载达梦JDBC驱动配置步骤1. 将驱动添加到IDEA2. 创建数据库连接连接参数

MySql中的数据库连接池详解

《MySql中的数据库连接池详解》:本文主要介绍MySql中的数据库连接池方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录mysql数据库连接池1、概念2、为什么会出现数据库连接池3、原理4、数据库连接池的提供商5、DataSource数据源6、DBCP7、C

pycharm远程连接服务器运行pytorch的过程详解

《pycharm远程连接服务器运行pytorch的过程详解》:本文主要介绍在Linux环境下使用Anaconda管理不同版本的Python环境,并通过PyCharm远程连接服务器来运行PyTorc... 目录linux部署pytorch背景介绍Anaconda安装Linux安装pytorch虚拟环境安装cu

Nginx设置连接超时并进行测试的方法步骤

《Nginx设置连接超时并进行测试的方法步骤》在高并发场景下,如果客户端与服务器的连接长时间未响应,会占用大量的系统资源,影响其他正常请求的处理效率,为了解决这个问题,可以通过设置Nginx的连接... 目录设置连接超时目的操作步骤测试连接超时测试方法:总结:设置连接超时目的设置客户端与服务器之间的连接

SQL 中多表查询的常见连接方式详解

《SQL中多表查询的常见连接方式详解》本文介绍SQL中多表查询的常见连接方式,包括内连接(INNERJOIN)、左连接(LEFTJOIN)、右连接(RIGHTJOIN)、全外连接(FULLOUTER... 目录一、连接类型图表(ASCII 形式)二、前置代码(创建示例表)三、连接方式代码示例1. 内连接(I

java如何通过Kerberos认证方式连接hive

《java如何通过Kerberos认证方式连接hive》该文主要介绍了如何在数据源管理功能中适配不同数据源(如MySQL、PostgreSQL和Hive),特别是如何在SpringBoot3框架下通过... 目录Java实现Kerberos认证主要方法依赖示例续期连接hive遇到的问题分析解决方式扩展思考总

Python中连接不同数据库的方法总结

《Python中连接不同数据库的方法总结》在数据驱动的现代应用开发中,Python凭借其丰富的库和强大的生态系统,成为连接各种数据库的理想编程语言,下面我们就来看看如何使用Python实现连接常用的几... 目录一、连接mysql数据库二、连接PostgreSQL数据库三、连接SQLite数据库四、连接Mo