【Druid 连接池】 学习~快速 掌握连接池基本原理

article/2025/9/15 13:23:22

文章目录

  • 前言
  • 提出问题:
    • 一、Druid连接池简述
  • 再次提出问题 :
    • 二、初始化连接池
    • 三、了解三个核心成员(三大线程)
    • 四、获取连接
    • 五、探活(连接池连接健康的检查机制)

前言

翻遍了各个大佬发的博客整理出来的学习资料,作为自己的学习总结和巩固,输入和输出结合才是提升学习成果最好的途径。在本文的后面会把大佬们的博客链接附上,感兴趣的同学也可以去看看。

那么正文开始啦 ~

学习一门知识当然是先提出自己问题,然后一步步的去找出问题的答案,加深自己的印象。

提出问题:

  • 问题1:Druid (德鲁伊)连接池是什么?
  • 问题2:为什么要用Druid连接池?
  • 问题3:Druid连接池的怎么使用?
  • 问题4:Druidd的基本原理是什么?(追本溯源,知其然方知其所以然~)

一、Druid连接池简述

  1. 讲到连接池我们要先知道什么是数据库连接池?
  • 数据库连接池是一个容器,是一个集合,里面容纳了数据库连接对象。通常项目启动会创建数据库连接池对象。使用Connection连接对象的时候,不用自己创建,而是从数据库连接池中获取连接对象Connection。使用结束之后不是销毁对象,而是将连接对象归还到数据库连接池中。
  1. 那么我们为什么使用数据库连接池?
  • 因为创建Connection对象很消耗资源,我们需要对消耗资源的对象进行重用。工作中不能直接创建Connection连接对象,必须中数据库连接池中获取连接对象Connection。
  1. 还有哪些数据库连接池?优缺点是什么?
  • DBCP java开源框架诞生前使用的

    • 优点:可以做到连接对象复用
    • 缺点:连接池效率不高,对开源框架支持不友好
  • C3P0 为开源框架提供的连接池

    • 优点:能够友好支持开源框架
    • 缺点:对分布式和微服务系统支持不友好,会突然断开连接
  1. 那么Druid连接池区别于其他连接池是什么?如何脱颖而出的?
  • Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。

  • 现在我们已经知道了什么是Druid连接池,那么他有什么特点呢?

    1. 简答解析:在程序启动时,预先创建指定数量的数据库连接,放入到池中,需要数据库连接时,直接从池中获取,使用结束之后(将当前状态设置为空闲状态),再重新放回。
    • 好处:实现复用,节省资源,提升效率
    1. 连接池对象如果没有被close关闭,则该连接对象会一直被占用,那么下次继续获取连接对象,则不会获取到该对象的hashcode;当连接池对象被close关闭,实际上并不是真正的关闭,而是将状态设值为空闲状态,然后下次还是会获取到当前连接池对象。
    • 好处:连接池中的connection可以重复使用

再次提出问题 :

  • 刚刚我们已经对Druid连接池有了简单的,初步的认识了,那么新的问题又来了~
  • 问题1:怎么使用Druid连接池?
  • 问题2:连接池是如何实现Java程序,druid连接池 和 MySQL之间的兼容的?
  • 问题3:Druid连接池是怎么创建连接?保证连接池的资源可以重复使用的?
  • 问题4:Druid连接池连接的探活机制又是什么?

二、初始化连接池

  1. 在了解连接池初始化之前,我们要先知道连接池是如何实现Java程序,druid连接池 和 MySQL之间的兼容的?
  • Java程序使用Alibaba的druid连接池,必须通过一个“配置文件 和 JDBC规范”告诉Alibaba的druid连接池,连接数据库的URL和账号密码…(配置文件信息:驱动,url,用户名,密码,初始化连接大小,最大活动连接大小,最大活动连接大小,最大活动连接使用完的最大等待时间)

  • 因此druud.properties是我的项目和Druid连接池交互的桥梁,连接池可以创建多少个对象都是通过这个配置文件告诉Druid连接池的

  • 注:JDBC规范(指定Java类与数据库服务器厂商之间的沟通规则,jdbc规范接口实现类由不同的关系型数据库服务器厂商以jar包的形式提供)

在这里插入图片描述
2. 在SpringBoot项目中是怎么初始化Druid连接池的?

  • 在SpringBoot项目中,spring.factories文件中,配置了Druid的自动配置类com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure,@bean的时候就会执行init()方法,这也就是为什么项目启动后会自动进行Druid的初始化。当然如果项目不是SpringBoot项目,可以在定义DataSource的时候手动调用init()方法。

  • JDBC基本参数配置含义:
    在这里插入图片描述

  1. 初始化连接池的基本流程
    1. 初始化filters
    • filters的来源主要有两个:一是通过系统参数配置:-Ddruid.filters=xxx,是个列表;二是通过SPI机制加载;主要是提供给用户扩展使用的,在获取连接的时候会通过责任链模式进行调用;
    1. 参数校验
    • 主要是校验参数配置的合法性,如必须 maxActive>0、maxActive >= minIdle、maxEvictableIdleTimeMillis >= minEvictableIdleTimeMillis等等;
    1. 初始化ExceptionSorter和validConnectionChecker
    • ● ExceptionSorter
      ExceptionSorter是Druid稳定性的保证。作用是:在数据库服务器重启、网络抖动、连接被服务器关闭等异常情况下,连接发生了不可恢复异常,将连接从连接池中移除,保证连接池在异常发生时情况下正常工作。
    • ● ValidConnectionChecker
      用来检测连接池中连接的可用性,如果检测连接不可用,则close掉;可用的话就继续放回连接池中。
  • 特别说明:

    • ExceptionSorter:官方的说明这是Druid连接池稳定性的保证,用于处理重大的不可恢复的异常,它是一个接口,不同的数据库有不同的实现类,mysql的处理类是 MySqlExceptionSorter,它的isExceptionFatal(SQLException e)方法中识别重大异常
    • Filter:这是Druid中的又一大特色,提供了强大的扩展功能,如连接池监控(连接池配置信息、SQL执行、并发、慢查询、执行时间区间分布等,由StatFilter实现)、防止SQL注入(WallFilter)、连接池信息日志输出(LogFilter)等。
    1. 初始化连接池
    • DruidConnectionHolder类型的数组,有3个池子(容量都是maxActive):
      connections:存放正常连接,我们常说的连接池就是指这个数组
      evictConnections:存放需要抛弃的连接;
      keepAliveConnections:存放需要进行活性检测的连接
      并生成initialSize个连接存放到connections中;
    1. 开启三个守护线程
    • createAndLogThread();
      createAndStartCreatorThread();
      createAndStartDestroyThread();

小结:

  • 看到初始化的这几个过程是不是一脸懵,这一整串的文字看起来又不怎么好理解,那么用白话文简单描述一下连接池的原理就可以对连接池初始化的过程有个简单的了解,当然最好是看源码啦~
  • DruidDataSource 是数据库连接池的核心部分,可以看做是 Druid 连接池租赁公司的老板。这家公司创立之初,老板会去中招聘其他三位核心成员:
  1. 创建连接池线程 CreateConnectionThread。主要职责就是创建连接,当连接不够的时候都是交给他创建,满足用户对连接的需求。
  2. 销毁连接线程 DestroyConnectionThread。主要职责线程池的销毁,将一些空闲连接、不健康连接清除,维持一个最小空闲连接数。
  3. 日志线程 LogStatsThread。可以看做公司的财务,每隔一段时间记录连接的消费和使用细节。
  • DruidDataSource 在初始化的时候就 创建以上三个线程。初始化时还会做以下几个事情:
    将 connections 初始化,大小设置为 maxActive。一旦创建好以后就不能发生变化了,连接最大数量不能改变。根据用户配置的 minActive 数量初始化连接,这样启动以后就有直接可用的连接。为了提高启动速度也可以将这个初始化工作进行异步化(druid.asyncInit 参数设置为true即可)。

  • 知识点:
    连接池数量最大不能超过 maxActive.
    最小连接数不能低于 minIdle

三、了解三个核心成员(三大线程)

  • 刚刚有提到在初始化线程池的时候会,创建三大线程,而这三大线程是连接池中最主要的成员之一,要想了解Druid的家族史,那么我们自然要对他们兄弟要有个简单的了解啦!
  1. CreateConnectionThread
  • 这个主要是一个负责创建连接。不是时刻都在创建连接,当没有可用连接时,用户线程进入等待挂起状态,并且连接数量小于maxActive 时才创建连接,否则挂起线程睡眠等待。
    创建完的连接直接存在 connections 末尾,并发一个notEmpty信号告诉所有在等待的线程。等待线程则从睡眠中恢复,再尝试去获取连接。
  1. DestroyConnectionThread
  • 该线程主要工作是将空闲及无效的连接销毁。默认情况下每1000ms 执行一次,可以通过timeBetweenEvictionRunsMillis 时间设置执行间隔。每次回收都是从connects 头部开始遍历;
    主要回收几类连接:
    1.连接的空闲时间大于 minEvictableIdleTimeMillis(连接保持空闲而不被驱逐的最小时间), 则进行回收。
    2.大于minIdle 部分的连接会被回收。保证连接池空闲连接不会太多。
    3.检查连接活跃度,不健康的连接则关闭。默认不检查,可以通过 druid.keepAlive 打开连接的健康检查。
  1. LogStatsThread
  • 主要负责实施记录 Druid的开销情况,包括当前连接池数量、activeCount、这期间关闭的连接数量等等。LogStatsThread 按配置的timeBetweenLogStatsMillis 的周期时间,定期执行。如果 timeBetweenLogStatsMillis 没有配置,则该线程将不会被开启。

四、获取连接

  1. 当用户需要连接时,调用 dataSource的getConnection 方法。 DruidDataSource 首先需要判断当前线程池是否有可用的连接,如果有可用的连接则采用LRU策略获取,直接从 connections 尾部获取一个连接返回。尾部的连接是当前连接池中最活跃的连接,要么是刚刚释放回去的连接,要么就是新创建的。因此最大程度保证连接是可用的,健康的。

  2. 如果当前连接池没有可用的连接,那么首先会发一个信号告诉 CreateConnectionThread 去创建新的连接,然后将自己挂起直到有新的可用连接。新的连接会从两个地方获得,一个是 CreateConnectionThread,一个是刚刚回收的连接。

  3. 用户使用的Connection 是代理代理类 DruidConnectionHolder, 在执行 close 时不是直接将连接关闭,而是将连接回收。
    回收的连接放在connects 末尾,存放完毕后发发一个 notEmpty 通知,通知正在等待连接的线程。

五、探活(连接池连接健康的检查机制)

  1. 为何要关注连接健康情况?
    • Mysql 连接超时时间默认8 小时,超时以后原连接失效。如果继续使用原连接操作数据库,则应用程序会直接报连接不可用,这种对数据操作比较敏感的应用会有一些影响。这种连接不可用的场景非常常见,比如因为服务与数据库之间存在一些网络波动、数据库热迁移、mysql 内置超时机制等。比较小的影响就是某一次数据库操作不可用,但也有可能引起致命的问题,例如引起JDBC 内部的循环等待远程数据,导致 CPU 过高。
    • 所以在生产开发时,需要关注连接的健康,如果连接不可用则废弃重新创建。
  1. 那么druid 如何做连接检查?
    1. Druid 提供了多个连接检查的时机点:获取连接时检查、归还连接时检查、空闲时检查

获取连接时检查:
就是每次拿到连接以后判断连接是否是可用的,如果可用则拿来使用,避免使用无效连接执行导致报错。如果连接不可用则直接销毁,重新获取。通过 testOnBorrow 参数来设置,默认是打开的

归还连接时检查:
每次连接使用完还给连接池前做一个健康检查,判断连接是否可用,不可用直接废弃。testOnReturn 设置归还时检查开关,默认是false

空闲时检查:
不是定时自动去检查,而是在调用时,如果连接空闲时间操作 timeBetweenEvictionRunsMillis 则检查一次,而不是每次检查一次。通过 testWhileIdle 参数设置,默认是 false

  • 空闲时检查和获取连接时检查是互斥的,二者只能选择一个。归还时检查可以和其他两个组合使用,例如获取连接时先检查一遍,归还时再检查一遍,但一般生产上很少这样使用,毕竟每次检查都有性能开销。

  • 就像每次做车检都需要有一些开销,连接检查也是。获取连接时检查和归还连接时检查都是对数据库操作都做一次校验,对于数据库变更频繁的系统是一笔很大的开销。所以一般会优先考虑空闲时检查机制,毕竟连接无效事件不是一个频繁事件。

    1. 另外Druid连接池有两种健康检查机制
    • validationQuery 查询机制

druid.validationQuery = select 1
用来检测连接是否有效的sql,要求是一个查询语句,常用select ‘x’。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
这种和我们以往理解的连接检查机制不同,系统间一般通过 ping 来判断连接健康性。ping 只要通过很少的网络传输就可以完成连接验证。而这种方式不仅需要传输validationQuery 语句,还需在数据库层面执行 sql 语句,注定比 ping 模式性能低。低版本的 druid 只支持该模式。

    • ValidConnectionChecker 机制

ValidConnectionChecker 可以为不同的数据库类型定制不同的 检查方式,同时支持自定义的连接健康检查方式。在高版本下默认使用该模式,如果没有设置或解析失败,则退回到 validationQuery。druid 启动时根据配置的数据源自动配置对应的 ValidConnectionChecker。
MySqlValidConnectionChecker 是mysql 的连接默认检查器,即数据库配置的连接驱动器为 com.mysql.jdbc.Driver。主要逻辑是判断JDBC是否支持 pingInternal 方式,如果支持则使用 ping, 否则退回使用 validationQuery。

小结:
1、只有mysql有ping方式检测连接可用性,而且默认情况下mysql是采用ping方式(其它数据库都是采用validateQuery);
2、如果要想通过配置的validateQuery来进行检测怎么办呢?根据MySqlValidConnectionChecker类的构造方法可以知道,需要配置jvm参数:-Ddruid.mysql.usePingMethod=false;
3、所以如果是mysql数据库,仅仅配置validateQuery是不行的,还要添加Ddruid.mysql.usePingMethod=false配置;
4、如果池中原本有超过minIdle个连接,那么经过回收之后,池中至少会保持minIdle个连接在里面;
5、连接回收的最终目的是将可用的连接放回池中,以达到连接循环利用的目的,这样可以节省性能。从以上回收的过程来看,在回收时会先进行一系列连接的校验,比如连接的可用性校验、连接超时校验等待,如果不符合校验条件,就直接将连接close掉;


通过上面的介绍,我们已经对Druid连接池有了简单的了解,知道了我们在程序中是怎么去初始化连接,获取连接的,以及在连接池中的连接是怎么保证连接的有效性的。怎么重复使用连接保证程序的性能的。

摘录原作者:

参考文献:
Druid系列(2)–连接池原理
Druid连接池核心原理(有源码图,感兴趣的同学可以去看看)


http://chatgpt.dhexx.cn/article/OHP5cq6m.shtml

相关文章

Druid连接池的基本配置与使用

Druid简介 Druid是阿里巴巴的开源连接池组件,是世界上最好的连接池之一。Druid能对数据库连接进行有效管理和重用,最大化程序执行的效率。连接池负责创建和管理连接,程序只负责取用与归还。 以下是我画的示意图: 下面我来给大家…

MySQL快捷键注释

MySQL语句注释快捷键显示为CtrlOemQuestion,对应于我们键盘上的是Ctrl?

Mysql 增加表注释

关注微信公共号:小程在线 关注CSDN博客:程志伟的博客 方法一: mysql添加表注释 ALTER TABLE 表名 COMMENT 注释字段; 实例: alter table test comment 测试表; 方法二: 在创建表的时候直接增加表注释…

mysql语句中的注释方法_MySQL语句注释方式简介

MySQL支持三种注释方式: 1.从‘#字符从行尾。 2.从‘-- 序列到行尾。请注意‘-- (双破折号)注释风格要求第2个破折号后面至少跟一个空格符(例如空格、tab、换行符等等)。 3.从/*序列到后面的*/序列。结束序列不一定在同一行中,因此该语法允许注释跨越多行…

MySQL注释:单行注释和多行注释,快进来理解

单行注释与多行注释 单行注释多行注释 单行注释 单行注释可以使用#注释符,#注释符后直接加注释内容 #注释内容单行注释可以使用- -注释符,- -注释符后需要加一个空格,注释才能生效。 -- 注释内容#和–的区别就是:#后面直接加注…

Mysql注释

Mysql注释 MySQL服务器支持3种注释风格: 从‘#’字符从行尾。 从‘-- ’序列到行尾。请注意‘-- ’(双破折号)注释风格要求第2个破折号后面至少跟一个空格符(例如空格、tab、换行符等等)。该语法与标准SQL注释语法稍有不同。 从/*序…

mysql注释乱码问题_mysql注释是中文乱码怎么办

mysql注释是中文乱码的解决办法:首先用“show variables like”命令查看数据库与服务端的字符集设置;然后执行语句为“set collation_serverutf8_general_ci;”即可。 mysql表注释乱码 问题 执行语句: alter table TABLE_XXX comment ‘中文号’; 结果: …

wepy开发小程序eslint报错error 'getApp' is not defined no-undef

wepy开发小程序使用getApp().globalData保存全局数据很方便,但是会在控制台看到很多报错:“error getApp is not defined no-undef”,这是eslint报错。 解决办法:在.eslintrc.js文件中加入 globals: { getApp: true } 转载于:ht…

微信小程序 关于获取用户信息的一种方法 “this.setData与getApp().globalData.userInfo“

获取用户信息&#xff1a;this.setData和getApp().globalData.userInfo; 最开始的WXML文件中&#xff0c;我们写下页面结构代码如下&#xff1a; <image class"userinfo-avatar" src"{{userInfo.avatarUrl}}" mode"cover"></image>…

JS交互逻辑——获取页面数据{{}}和全局数据const app = getApp()

JS交互逻辑 在小程序里边&#xff0c;我们就通过编写 JS 脚本文件来处理用户的操作 app.js中最外层需要App({}) &#xff0c;该函数是微信平台提供的api App({// 全局数据&#xff0c;可以在所有的子页面中使用mydata: {n: 100} }) // 页面获取全局数据的方式,app表示小程序的…

微信小程序getApp().globalData.user无法获取值

一、debug过程 在进行微信小程序的开发过程中&#xff0c;我利用如下代码获取全局变量中的username的值&#xff0c;但其结果缺如图中所示的只向数据库中存入了对应的类型即object Object。 username: getApp().globalData.user.username, 数据库结果 其中&#xff0c;全局变量…

微信小程序const app=getApp()的作用,实现page页面获取全局数据

app.js的作用 app.js是整个小程序项目的入口文件&#xff0c; 如果小程序要运行&#xff0c;第一个被执行的文件就是app.js&#xff0c;第一个被执行的代码是app.js中的onLaunch方法 在app.js中可以书写小程序的全局业务逻辑 在app.js里面&#xff0c;写上一些需要的东西&am…

uniapp中的getApp().globalDate.

获取全局变量&#xff1a; getApp().globalDate1.uni-app 在app.vue文件export default里面定义globalData&#xff0c;在其他文件则用getApp&#xff08;&#xff09;.globalData获取 2.在当前文件&#xff08;app.vue&#xff09;里面获取globalData需要用this.$options.glob…

解释微信小程序const app=getApp()的作用

const appgetApp()用来获取全局变量 const appgetApp()这就话通常在pages下的页面的js中写 来调用app.js中的globalData{} 直白一点说就是在app.js的globalData{}里写的东西 在其他页面的js的onshow中可以直接调用 例&#xff1a;

微信小程序 app=getApp()公共变量在页面间传值技巧。

1.公共变量 在开发过程中&#xff0c;避免不了会使用公共变量&#xff0c;记录共享对象状态、数据最简单的方式就是创建创建公共变量&#xff1b; 当业务逻辑变多&#xff0c;还采用这种思想就会变得危险&#xff0c;代码逻辑变得不清晰&#xff0c;慢慢就有一种代码坏味道。 …

【uniapp】uniapp的globalData以及getApp():

文章目录 一、说明文档&#xff1a;二、使用&#xff1a; 一、说明文档&#xff1a; 【官方文档】https://uniapp.dcloud.net.cn/collocation/App.html#globaldata 二、使用&#xff1a;

getapp.php,getApp.php

下载RiseClub $iosUrl https://itunes.apple.com/cn/app/riseclub/id869364498?lzh&mt8; $yingyongbao http://a.app.qq.com/o/simple.jsp?pkgnamecom.rusi.club&g_f994302; $androidUrl http://club.risecenter.com/riseclub.v1.1.2.apk; if(isset($_GET[go2Andr…

微信小程序App()方法与getApp()方法

微信小程序App()方法与getApp()方法 二者含义: App()有以下作用&#xff1a; 注册一个小程序小程序的入口方法 getApp()主要用于获取全局对象&#xff0c;然后进行全局变量和全局方法的使用 例如&#xff1a; app.js 中的 App() App({onLaunch: function(options) {cons…

《算法导论》+第四版++答案

在有关算法的书中&#xff0c;有一些叙述非常严谨&#xff0c;但不够全面&#xff1b;另一些涉及了大量的题材&#xff0c;但又缺乏严谨性。本书将严谨性和全面性融为一体&#xff0c;深入讨论各类算法&#xff0c;并着力使这些算法的设计和分析能为各个层次的读者接受。全书各…

《算法》第四版官网库及数据文件

《算法》第四版官网库及数据文件分享 最近购买了《算法》这本书&#xff0c;本来想着只是看看的&#xff0c;但是觉得还是想写写书上的代码&#xff0c;结果发现这本书自己用了一个库来实现书上的代码&#xff0c;于是我去下载这个库以及测试数据的文件。但是过程意外的艰辛&a…