连接池 Druid (二) - 连接回收 DestroyThread

接上一篇文章,研究Druid连接池的连接回收线程DestroyThread,通过调用destroyTask.run->DruidDataSourcek.shrink完成过期连接的回收。

DruidDataSourcek.shrink

理解DruidDataSourcek的连接回收方法shrink有一个必要前提:Druid的getConnection方法总是从connectoins的尾部获取连接,所以闲置连接最有可能出现在connections数组的头部,闲置超期需要被回收的连接也应该处于connections的头部(数组下标较小的对象)。

在这个基础上,我们开始分析代码。

public void shrink(boolean checkTime, boolean keepAlive) {try {lock.lockInterruptibly();} catch (InterruptedException e) {return;}boolean needFill = false;int evictCount = 0;int keepAliveCount = 0;int fatalErrorIncrement = fatalErrorCount - fatalErrorCountLastShrink;fatalErrorCountLastShrink = fatalErrorCount;

首先获取锁资源,并初始化控制变量。

       try {if (!inited) {return;}final int checkCount = poolingCount - minIdle;final long currentTimeMillis = System.currentTimeMillis();for (int i = 0; i < poolingCount; ++i) {DruidConnectionHolder connection = connections[i];if ((onFatalError || fatalErrorIncrement > 0) && (lastFatalErrorTimeMillis > connection.connectTimeMillis))  {keepAliveConnections[keepAliveCount++] = connection;continue;}

如果初始化尚未完成,则不能开始做清理动作,直接返回。

计算checkCount,checkCount的意思是本次需要清理、或者需要检查的连接数量,checkCount等于连接池数量减去参数设置的需要保持的最小空闲连接数。很好理解,清理完成之后仍然需要确保最小空闲连接数。

之后循环逐个检查连接池connections中的所有连接,从头部(connections[0])开始。

如果清理过程中发生了错误,并且错误发生的时间是在当前连接的连接获取时间之后,则将当前连接放入keepAliveConnections中,继续检查下一个连接。

然后:

                if (checkTime) {if (phyTimeoutMillis > 0) {long phyConnectTimeMillis = currentTimeMillis - connection.connectTimeMillis;if (phyConnectTimeMillis > phyTimeoutMillis) {evictConnections[evictCount++] = connection;continue;}}long idleMillis = currentTimeMillis - connection.lastActiveTimeMillis;if (idleMillis < minEvictableIdleTimeMillis&& idleMillis < keepAliveBetweenTimeMillis) {break;}if (idleMillis >= minEvictableIdleTimeMillis) {if (checkTime && i < checkCount) {evictConnections[evictCount++] = connection;continue;} else if (idleMillis > maxEvictableIdleTimeMillis) {evictConnections[evictCount++] = connection;continue;}}if (keepAlive && idleMillis >= keepAliveBetweenTimeMillis) {keepAliveConnections[keepAliveCount++] = connection;}}

这段代码对应的条件是checkTime,checkTime的意思是:是否检查数据库连接的空闲时间,是调用shrink方法时传入的,destoryThread任务调用shrink方法时传入的是true,所以会走到这段代码逻辑中。

如果参数设置的phyTimeoutMillis,即物理连接的超时时间>0的话,则检查当前连接创建以来到现在的时长如果已超时的话,当前连接放入evictConnections中,准备回收。

然后计算当前连接的空闲时间idleMillis,如果空闲时间小于参数设置的连接最小空闲回收时间minEvictableIdleTimeMillis,并且也小于保持存活时间keepAliveBetweenTimeMillis,则结束当前循环,不再检查连接池中剩余连接。

这里的逻辑其实也是基于connections的特性:数组第一个元素空闲时间最长,从左到右的空闲时间越来越短,如果从左到右检查过程中发现当前元素空闲时间没有达到需要回收的时长的话,就没必要检查连接池中后续的元素了。

否则如果当前连接空闲时长idleMillis大于等于minEvictableIdleTimeMillis的话,则判断checkTime && i < checkCount的话则将当前连接放入evictConnections中准备回收。此处i < checkCount的意思就是,回收后的连接数量仍然能够确保最小空闲连接数的要求,则直接回收当前连接。

否则,就是i>=checkCount情况,这种情况下如果发生回收的话,必然会导致连接池中的剩余连接数不能满足参数设置的最小空闲连接数的要求、必须要重新创建连接了。但是如果空闲时长大于maxEvictableIdleTimeMillis,也必须是要回收的,所以,将当前连接放入evictConnections准备回收。

有关连接回收,多说一句,连接池参数maxEvictableIdleTimeMillis一般会根据数据库端的参数进行配置,连接闲置超过一定时长的话,数据库会主动关闭连接,这种情况下即使应用端连接池不关闭连接,该连接也不可用了。所以为了确保连接可用,一般情况下应用端数据库连接池的maxEvictableIdleTimeMillis应该设置为小于数据库端的最大空闲时长。

然后判断如果keepAlive(参数设置,默认false)并且当前连接的空闲时间idleMillis大于等于参数设置的保活时长keepAliveBetweenTimeMillis的话,则当前连接放入keepAliveConnections中保活。

接下来:

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

就是checkTime=false的情况,意思就是不检查空闲时长,那么能够确保最小空闲连接数的前提下,其他连接都可以回收,所以要把connections中小于checkCount((i < checkCount)的连接全部放入evictConnections中回收。

连接池中的连接检查完毕,该回收连接放在evictConnections中,该保活的放在keepAliveConnections中。接下来的代码开始真正处理回收和保活。

首先清理连接池connections:

            int removeCount = evictCount + keepAliveCount;if (removeCount > 0) {System.arraycopy(connections, removeCount, connections, 0, poolingCount - removeCount);Arrays.fill(connections, poolingCount - removeCount, poolingCount, null);poolingCount -= removeCount;}

计算需要移除的连接数量removeCount等于回收数量与保活数量之和,然后将connections中的位于removeCount之后的元素前移,使其处于connnections数组的头部,并重新计算poolingCount。

接下来计算保活数量:

            keepAliveCheckCount += keepAliveCount;if (keepAlive && poolingCount + activeCount < minIdle) {needFill = true;}} finally {lock.unlock();}

累加keepAliveCheckCount,并且判断如果连接池数量小于最小空闲数的话,设置needFill为true。

释放锁资源。

然后回收连接:

        if (evictCount > 0) {for (int i = 0; i < evictCount; ++i) {DruidConnectionHolder item = evictConnections[i];Connection connection = item.getConnection();JdbcUtils.close(connection);destroyCountUpdater.incrementAndGet(this);}Arrays.fill(evictConnections, null);}

将evictConnections中的连接逐个回收:关闭连接,并累加destroyCount,并重新初始化evictConnections。

接下来处理保活连接:

       if (keepAliveCount > 0) {// keep orderfor (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;if (validate) {holer.lastKeepTimeMillis = System.currentTimeMillis();boolean putOk = put(holer, 0L);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);}

逐个处理keepAliveConnections中的连接。

调用validateConnection方法检查当前连接是否可用,如果连接仍然可用,则更新连接的lastKeepTimeMillis为当前系统时间后,调用put方法将连接重新放回连接池connections中。如果放回失败则关闭连接。连接关闭后检查当前连接池数量activeCount + poolingCount <= minIdle则调用emptySignal();创建连接。

之后将keepAliveCount加入到连接统计分析数据中,重置keepAliveConnections数组。

最后:

       if (needFill) {lock.lock();try {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,说明当前连接池数量不能满足参数设置的最小空闲连接数,则获取锁资源,计算需要创建的连接数,调用emptySignal();创建连接填充连接池直到连接数满足要求。

否则,如果发生错误onFatalError,说明有可能创建连接发生错误,则调用emptySignal(),检查并继续创建连接。

Druid连接回收部分的代码分析完毕!

小结

通过两篇文章学习分析了Druid连接池的初始化及连接回收过程,还有连接获取及关闭两部分重要内容,下一篇文章继续分析。

Thanks a lot!

上一篇 连接池 Druid (一) - 初始化过程
下一篇 连接池 Druid (三) - 获取连接 getConnection

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

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

相关文章

离散数学-函数

1、函数的概念 1&#xff09;函数定义 定义&#xff1a;设 x &#xff0c; y是集合&#xff0c;f是x到y的二元关系&#xff0c;若对每个x属于X&#xff0c;都有唯一的y属于Y&#xff0c;使得<x,y>属于f&#xff0c;则称f是x到y的函数或映射&#xff0c;记作&#xff1a…

在vscode编辑器中,vetur和volar冲突

在vscode编辑器中 vetur插件会把vue3项目当成vue2去检查&#xff0c;然后出现了eslint报错 在项目的 package.json 中添加以下代码&#xff0c;并重启编辑器就可以了 // package.json"eslintConfig": {"rules": {"vue/no-multiple-template-root&qu…

深入理解CopyOnWriteArrayList源码分析

上篇推荐&#xff1a;Java中快速失败 (fail-fast) 机制 CopyOnWriteArrayList简介 CopyOnWriteArrayList是java.util.concurrent包下提供的一个线程安全的ArrayList。它通过一个简单的策略来保证线程安全&#xff1a;当我们需要修改列表时&#xff08;增加、删除、修改等操作&…

102 cesium 切换底图为黑色

1.切换cesium底图为黑色 // 底图const baseLayer viewer.imageryLayers.get(0);if (baseLayer.show) {baseLayer.show false;viewer.scene.globe.baseColor Cesium.Color.BLACK;} else {baseLayer.show true;} 2.地下模式 async toggleUnderground(item: any) {item.activ…

电子学会C/C++编程等级考试2023年03月(四级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:最佳路径 如下所示的由正整数数字构成的三角形: 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,和最大的路径称为最佳路径。你的任务就是求出最…

Linux之重谈文件和c语言文件接口

重谈文件 文件 内容 属性, 所有对文件的操作都是: a.对内容操作 b.对属性操作 关于文件 一&#xff1a; 即使文件的内容为空&#xff0c;该文件也会在磁盘上也会占空间&#xff0c;因为文件不仅仅只有内容还有文件对应的属性&#xff0c;文件的内容会占用空间, 文件的属性也…

gitlab 迁移-安装-还原

文章目录 一、备份原有Gitlab1、备份清单2、备份执行 二、卸载删除原有Gitlab1、停止Gitlab2、卸载Gitlab3、查看Gitlab进程4、杀死进程5、删除所有包含Gitlab文件 三、安装Gitlab1、添加镜像地址2、安装依赖3、安装防火墙4、下载安装Gitlab5、配置Gitlab6、启动并访问 四、还原…

Linux基本指令(2.0)

周边知识&#xff1a; 1.Linux中&#xff0c; 一切皆文件 构建大文件 输入如下shell命令 i1; while [ $i -le 10000]; do echo "hello Linux $i"; let i; done 此时大文件已经创建在big.txt 此时我们发现cat查看无法查看开始内容 我们使用more 当占满一屏之后就不…

Unity-Shader - 2DSprite描边效果

实现一个简单的2D精灵图描边效果&#xff0c;效果如下 实现思路&#xff1a; 可以通过判断该像素周围是否有透明度为 0的值&#xff0c;如果有&#xff0c;则说明该像素位于边缘。 所以我们需要打开alpha blend&#xff0c;即&#xff1a; Blend SrcAlpha OneMinusSrcAlpha&am…

单实例应用程序

2023年12月6日&#xff0c;周三凌晨 什么是单实例应用程序 单实例应用程序可以确保在同一时间只有一个应用程序实例在运行。 通常情况下&#xff0c;当用户尝试再次启动一个已经启动过的应用程序时&#xff0c;操作系统会打开一个新的实例。但有些情况下&#xff0c;我们可能…

js中的栈(stack)和堆(heap)

什么是堆什么是栈&#xff1f; 程序运行时候&#xff0c;需要内存空间存放数据。系统划分出的两种内存空间就叫做stack&#xff08;栈&#xff09;和heap&#xff08;堆&#xff09;。 栈&#xff08;stack&#xff09;&#xff1a;由操作系统自动分配内存空间&#xff0c;自…

透明度值和注意点

透明度 透明度分为256阶&#xff08;0-255&#xff09;&#xff0c;计算机上用16进制表示为&#xff08;00-ff&#xff09;。透明就是0阶&#xff0c;不透明就是255阶,如果50%透明就是127阶&#xff08;256的一半当然是128&#xff0c;但因为是从0开始&#xff0c;所以实际上是…

react项目中使用video标签设置自动播放并未及时播放解决

react项目中使用video标签设置autoplay,但是视频不会直接播放&#xff0c;会加载一段时间后才会自动播放。 解决&#xff1a; 手动调用play方法 const videoRef useRef();useEffect(() > { if(videoRef?.current){if(videoRef?.current.paused){videoRef?.current.pla…

leetcode:1422. 分割字符串的最大得分(python3解法)

难度&#xff1a;简单 给你一个由若干 0 和 1 组成的字符串 s &#xff0c;请你计算并返回将该字符串分割成两个 非空 子字符串&#xff08;即 左 子字符串和 右 子字符串&#xff09;所能获得的最大得分。 「分割字符串的得分」为 左 子字符串中 0 的数量加上 右 子字符串中 1…

Android 12.0 Folder文件夹全屏后文件夹图标列表居中时拖拽app到桌面的优化

1.概述 在12.0的系统rom产品开发中,在Launcher3中在目前的产品需求开发中,对于Launcher3中的文件夹Folder的布局UI 进行了定制化的需求要求把Folder修改为全屏,然后在中间显示文件夹图标的列表,这时候如果Folder是全屏的话,如果拖拽文件夹列表中的app图标,只有拖拽 到屏…

html复习

html form表单作用是收集数据提交 input框体控件不在form不适用于表单提交 可编辑性:contenteditable 提示值消失&#xff1a;placeholder&#xff0c;value是初始数据 块标签&#xff1a;单独占有一个空间&#xff0c;独占一行&#xff0c;标签遵循从上到下排列。table、d…

UEC++ 探索虚幻5笔记(捡金币案例) day12

吃金币案例 创建金币逻辑 之前的MyActor_One.cpp&#xff0c;直接添加几个资源拿着就用 //静态网格UPROPERTY(VisibleAnywhere, BlueprintReadOnly)class UStaticMeshComponent* StaticMesh;//球形碰撞体UPROPERTY(VisibleAnywhere, BlueprintReadWrite)class USphereCompone…

【Linux知识点汇总】04 Linux软件包管理器RPM常用命令

RPM&#xff08;Red Hat Package Manager&#xff09;是一种用于在基于Red Hat的Linux发行版中安装、卸载、更新和管理软件包的工具 查看和显示命令 说明命令查看已安装的rpm包rpm -qa查询某个rpm包rpm -q pkg_name查看已安装rpm包提供的配置⽂件rpm -qc pkg_name查看⼀个包安…

【水】pytorch:torch.reshape和torch.Tensor.view的区别

【水】pytorch&#xff1a;torch.reshape和torch.Tensor.view的区别 注&#xff1a;本篇仅为学习笔记&#xff0c;请谨慎参考&#xff0c;如有错误请评论指出。 参考&#xff1a;Pytorch: view()和reshape()的区别&#xff1f;他们与continues()的关系是什么&#xff1f; 两者…

Flink流批一体计算(23):Flink SQL之多流kafka写入多个mysql sink

目录 1. 准备工作 生成数据 创建数据表 2. 创建数据表 创建数据源表 创建数据目标表 3. 计算 WITH子句 1. 准备工作 生成数据 source kafka json 数据格式 &#xff1a; topic case_kafka_mysql&#xff1a; {"ts": "20201011","id"…