连接池 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/199574.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024年天津财经大学珠江学院专升本专业课考试《经济学》考试大纲

天津财经大学珠江学院2024年高职升本科专业课考试《经济学》考试大纲 一、本大纲系天津财经大学珠江学院2024年高职升本科《经济学》课程考试大纲。所列考试范围出自郑健壮、王培才主编的教材《经济学基础&#xff08;第二版&#xff09;》&#xff0c;清华大学出版社&#xf…

【Python】pptx文件转pdf

要将PPTX文件转换为PDF格式&#xff0c;你可以使用Python的python-pptx库来读取PPTX文件&#xff0c;然后使用comtypes库在Windows上或unoconv在Linux上来进行转换。但是&#xff0c;需要注意的是&#xff0c;comtypes依赖于Microsoft Office&#xff0c;而unoconv依赖于LibreO…

线程控制.

线程已经成为调度的基本单位了&#xff0c;每一个线程都属于同一个地址空间中&#xff0c;所有的线程都属于同一个进程 换句话任何一个线程尝试调用geipid它应该是同一个pid 可是OS选择线程时&#xff0c;他怎么知道哪个线程是主线程&#xff1f;哪个是新线程&#xff1f;线程也…

C语言-字符串变量

字符串变量 char* s “Hello, world!”&#xff1b; s是一个指针&#xff0c;初始化为指向一个字符串常量 由于这个常量所在的地方&#xff0c;所以实际上s是const char* s&#xff0c;但是由于历史的原因&#xff0c;编译器接受不带const的写法但是试图对s所指的字符串做写…

CAD画图-模型和布局区别,视图命令MV使用(用于局部放大显示)

模型和布局的图像区别 模型的图像&#xff1a; 是我们常编辑的cad文件&#xff0c;我们可以对里面内容进行编辑和测量等操作 布局的图像&#xff1a;为了可以更好的看到每个部件的相对位置&#xff0c;但对于里面的点位的标注就不行了&#xff0c;但可以对图像中的某些部位进行…

Vue3 toRef,toRefs | toRaw

toRef / toRefs 使 获取到的 响应式对象的属性 也具有响应式 也就是单独修改获取到的属性 原本响应式对象也会更新&#xff1b;反之亦然。 toRefs 主要方便解构语法&#xff0c;底层也是调用 toRef toRaw 去响应式结果 结果已经保存在内部属性中&#xff0c;toRaw 直接访问…

linux系统下XDMA驱动的安装与测试问题总结

文章目录 目录 概要 XDMA IP核配置 PCIe ID项 PCIe:MISC项 XDMA驱动代码介绍

数据科学:Scipy、Scikit-Learn笔记

数据科学&#xff1a;Numpy、Pandas笔记 数据科学&#xff1a;Matplotlib、Seaborn笔记 数据科学&#xff1a;Numpy、Pandas、Matplotlib、Seaborn、Scipy、Scikit-Learn Scipystats Scikit-Learn参考 Scipy 模块作用scipy.cluster矢量量化/Kmeansscipy.constants物理和数学…

软件项目功能测试框架

测试用例的编写需要按照一定的思路进行&#xff0c;而不是想到哪写到哪&#xff0c;一般测试机制成熟的公司都会有公司自己自定义的测试用例模板&#xff0c;以及一整套的测试流程关注点&#xff0c;当然我们自己在测试生涯中也应当积累一套自己的测试框架&#xff0c;所有功能…

二极管:TVS瞬态抑制二极管

一、什么是TVS二极管 TVS&#xff08;Transient Voltage Suppressors&#xff09;&#xff0c;即瞬态电压抑制器&#xff0c;又称雪崩击穿二极管。 TVS二极管的符号如下图所示 什么是雪崩击穿 雪崩击穿是有必要了解一下的&#xff0c;不然后面还有齐纳击穿&#xff0c;搞不…

第二十一章 网络通信

21.1 网络程序设计基础 网络程序设计编写的是与其他计算机进行通信的程序。 局域网与互联网 服务器是指提供信息的计算机或程序&#xff0c;客户机是指请求信息的计算机或程序。网络用于连接服务器与客户机&#xff0c;实现两者间的相互通信。 网络协议 网络协议规定了计算…

RK356x U-Boot研究所(命令篇)3.12 mtd命令的用法

平台U-Boot 版本Linux SDK 版本RK356x2017.09v1.2.3文章目录 一、命令配置二、命令定义三、命令用法3.1 mtd list3.2 mtd write/read3.3 mtd erase3.4 mtd dump3.5 mtd bad一、命令配置 .config 配置文件需要有以下配置: CONFIG_CMD_MTD=y

P4715 【深基16.例1】淘汰赛-仅思路

首先从题干要求入手&#xff0c;我们可以了解到题目要求是二进一&#xff0c;不难想到这是二叉树的题 再来&#xff0c;从题干可以知道&#xff0c;我们所采用的结构体除了需要有树的两个左右节点指针外&#xff0c;还需要两个变量用来储存“能力值”和“编号” 在这道题中&am…

竞赛选题 题目:基于深度学习的图像风格迁移 - [ 卷积神经网络 机器视觉 ]

文章目录 0 简介1 VGG网络2 风格迁移3 内容损失4 风格损失5 主代码实现6 迁移模型实现7 效果展示8 最后 0 简介 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习卷积神经网络的花卉识别 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c…

CoreDNS实战(三)-CoreDNS+ETCD实现DNS负载均衡

1 概述 DNS负载均衡简单来说就是通过一个域名绑定多个IP地址&#xff0c;当客户端访问域名时&#xff0c;DNS服务器将轮询返回其中一个IP&#xff0c;实现客户端分流的作用。 在K8s环境中CoreDNS作为容器服务的DNS服务器&#xff0c;那么就可以通过CoreDNS来实现DNS负载均衡&a…

【Linux】基础IO--重定向理解Linux下一切皆文件缓冲区

文章目录 一、重定向1.什么是重定向2.dup2 系统调用3.理解输入重定向、输出重定向和追加重定向4.简易shell完整实现 二、理解linux下一切皆文件三、缓冲区1.为什么要有缓冲区2.缓冲区的刷新策略3.缓冲区的位置4.实现一个简易的C语言缓冲区5.内核缓冲区 一、重定向 1.什么是重定…

tcexam 本地容器化搭建

PS: 参考[使用docker搭建tcexam在线考试平台-CSDN博客],在本机Windows 11下使用 wsl2 和 docker-desktop,只记录和总结容器创建和install步骤,重点是踩的坑和解决的方法。 1. 解读 tcexam_docker 1.1 Git下载 tcexam_docker 工程源码至宿主机的本地目录 这里的宿主机既可以…

Java---类的继承

文章目录 1. 理解继承2. 继承概述3. 代码块理解4. 继承的好处与弊端5. 继承中变量的访问特点6. super关键字7. 继承中构造方法访问特点8. 继承中成员方法访问特点9. 方法重写10. 方法重写注意事项11. Java继承注意事项 1. 理解继承 2. 继承概述 1. 继承是面向对象的三大特征之一…

免费好用的api整理合集

免费api接口大全分享~ 二维码生成器&#xff1a;可生成普通二维码、带图片的艺术二维码&#xff08;黑白与彩色&#xff09;、动态二维码&#xff08;黑白与彩色&#xff09;。 实人认证&#xff08;人像三要素&#xff09;&#xff1a;输入姓名、身份证号码和一张人脸照片&a…

Java的严格计算部分

注释&#xff1a;在Math类中&#xff0c;为了达到最快的性能&#xff0c;所有的方法都使用计算机浮点单元中的例程。 如果得到一个完全可预测的结果比运行速度更重要的话&#xff0c;那么就应该使用StrictMath类。 它使用“自由发布的Math库”&#xff08;fdlibm&#xff09;实…