文章目录
- 前言
- 提出问题:
- 一、Druid连接池简述
- 再次提出问题 :
- 二、初始化连接池
- 三、了解三个核心成员(三大线程)
- 四、获取连接
- 五、探活(连接池连接健康的检查机制)
前言
翻遍了各个大佬发的博客整理出来的学习资料,作为自己的学习总结和巩固,输入和输出结合才是提升学习成果最好的途径。在本文的后面会把大佬们的博客链接附上,感兴趣的同学也可以去看看。
那么正文开始啦 ~
学习一门知识当然是先提出自己问题,然后一步步的去找出问题的答案,加深自己的印象。
提出问题:
- 问题1:Druid (德鲁伊)连接池是什么?
- 问题2:为什么要用Druid连接池?
- 问题3:Druid连接池的怎么使用?
- 问题4:Druidd的基本原理是什么?(追本溯源,知其然方知其所以然~)
一、Druid连接池简述
- 讲到连接池我们要先知道什么是数据库连接池?
- 数据库连接池是一个容器,是一个集合,里面容纳了数据库连接对象。通常项目启动会创建数据库连接池对象。使用Connection连接对象的时候,不用自己创建,而是从数据库连接池中获取连接对象Connection。使用结束之后不是销毁对象,而是将连接对象归还到数据库连接池中。
- 那么我们为什么使用数据库连接池?
- 因为创建Connection对象很消耗资源,我们需要对消耗资源的对象进行重用。工作中不能直接创建Connection连接对象,必须中数据库连接池中获取连接对象Connection。
- 还有哪些数据库连接池?优缺点是什么?
-
DBCP java开源框架诞生前使用的
- 优点:可以做到连接对象复用
- 缺点:连接池效率不高,对开源框架支持不友好
-
C3P0 为开源框架提供的连接池
- 优点:能够友好支持开源框架
- 缺点:对分布式和微服务系统支持不友好,会突然断开连接
- 那么Druid连接池区别于其他连接池是什么?如何脱颖而出的?
-
Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。
-
现在我们已经知道了什么是Druid连接池,那么他有什么特点呢?
-
- 简答解析:在程序启动时,预先创建指定数量的数据库连接,放入到池中,需要数据库连接时,直接从池中获取,使用结束之后(将当前状态设置为空闲状态),再重新放回。
-
- 好处:实现复用,节省资源,提升效率
-
- 连接池对象如果没有被close关闭,则该连接对象会一直被占用,那么下次继续获取连接对象,则不会获取到该对象的hashcode;当连接池对象被close关闭,实际上并不是真正的关闭,而是将状态设值为空闲状态,然后下次还是会获取到当前连接池对象。
-
- 好处:连接池中的connection可以重复使用
再次提出问题 :
- 刚刚我们已经对Druid连接池有了简单的,初步的认识了,那么新的问题又来了~
- 问题1:怎么使用Druid连接池?
- 问题2:连接池是如何实现Java程序,druid连接池 和 MySQL之间的兼容的?
- 问题3:Druid连接池是怎么创建连接?保证连接池的资源可以重复使用的?
- 问题4:Druid连接池连接的探活机制又是什么?
二、初始化连接池
- 在了解连接池初始化之前,我们要先知道连接池是如何实现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基本参数配置含义:
- 初始化连接池的基本流程
-
- 初始化filters
-
- filters的来源主要有两个:一是通过系统参数配置:-Ddruid.filters=xxx,是个列表;二是通过SPI机制加载;主要是提供给用户扩展使用的,在获取连接的时候会通过责任链模式进行调用;
-
- 参数校验
-
- 主要是校验参数配置的合法性,如必须 maxActive>0、maxActive >= minIdle、maxEvictableIdleTimeMillis >= minEvictableIdleTimeMillis等等;
-
- 初始化ExceptionSorter和validConnectionChecker
-
- ● ExceptionSorter
ExceptionSorter是Druid稳定性的保证。作用是:在数据库服务器重启、网络抖动、连接被服务器关闭等异常情况下,连接发生了不可恢复异常,将连接从连接池中移除,保证连接池在异常发生时情况下正常工作。
- ● ExceptionSorter
-
- ● ValidConnectionChecker
用来检测连接池中连接的可用性,如果检测连接不可用,则close掉;可用的话就继续放回连接池中。
- ● ValidConnectionChecker
-
特别说明:
-
- ExceptionSorter:官方的说明这是Druid连接池稳定性的保证,用于处理重大的不可恢复的异常,它是一个接口,不同的数据库有不同的实现类,mysql的处理类是 MySqlExceptionSorter,它的isExceptionFatal(SQLException e)方法中识别重大异常
-
- Filter:这是Druid中的又一大特色,提供了强大的扩展功能,如连接池监控(连接池配置信息、SQL执行、并发、慢查询、执行时间区间分布等,由StatFilter实现)、防止SQL注入(WallFilter)、连接池信息日志输出(LogFilter)等。
-
- 初始化连接池
-
- DruidConnectionHolder类型的数组,有3个池子(容量都是maxActive):
connections:存放正常连接,我们常说的连接池就是指这个数组
evictConnections:存放需要抛弃的连接;
keepAliveConnections:存放需要进行活性检测的连接
并生成initialSize个连接存放到connections中;
- DruidConnectionHolder类型的数组,有3个池子(容量都是maxActive):
-
- 开启三个守护线程
-
- createAndLogThread();
createAndStartCreatorThread();
createAndStartDestroyThread();
- createAndLogThread();
小结:
- 看到初始化的这几个过程是不是一脸懵,这一整串的文字看起来又不怎么好理解,那么用白话文简单描述一下连接池的原理就可以对连接池初始化的过程有个简单的了解,当然最好是看源码啦~
- DruidDataSource 是数据库连接池的核心部分,可以看做是 Druid 连接池租赁公司的老板。这家公司创立之初,老板会去中招聘其他三位核心成员:
- 创建连接池线程 CreateConnectionThread。主要职责就是创建连接,当连接不够的时候都是交给他创建,满足用户对连接的需求。
- 销毁连接线程 DestroyConnectionThread。主要职责线程池的销毁,将一些空闲连接、不健康连接清除,维持一个最小空闲连接数。
- 日志线程 LogStatsThread。可以看做公司的财务,每隔一段时间记录连接的消费和使用细节。
-
DruidDataSource 在初始化的时候就 创建以上三个线程。初始化时还会做以下几个事情:
将 connections 初始化,大小设置为 maxActive。一旦创建好以后就不能发生变化了,连接最大数量不能改变。根据用户配置的 minActive 数量初始化连接,这样启动以后就有直接可用的连接。为了提高启动速度也可以将这个初始化工作进行异步化(druid.asyncInit 参数设置为true即可)。 -
知识点:
连接池数量最大不能超过 maxActive.
最小连接数不能低于 minIdle
三、了解三个核心成员(三大线程)
- 刚刚有提到在初始化线程池的时候会,创建三大线程,而这三大线程是连接池中最主要的成员之一,要想了解Druid的家族史,那么我们自然要对他们兄弟要有个简单的了解啦!
- CreateConnectionThread
- 这个主要是一个负责创建连接。不是时刻都在创建连接,当没有可用连接时,用户线程进入等待挂起状态,并且连接数量小于maxActive 时才创建连接,否则挂起线程睡眠等待。
创建完的连接直接存在 connections 末尾,并发一个notEmpty信号告诉所有在等待的线程。等待线程则从睡眠中恢复,再尝试去获取连接。
- DestroyConnectionThread
- 该线程主要工作是将空闲及无效的连接销毁。默认情况下每1000ms 执行一次,可以通过timeBetweenEvictionRunsMillis 时间设置执行间隔。每次回收都是从connects 头部开始遍历;
主要回收几类连接:
1.连接的空闲时间大于 minEvictableIdleTimeMillis(连接保持空闲而不被驱逐的最小时间), 则进行回收。
2.大于minIdle 部分的连接会被回收。保证连接池空闲连接不会太多。
3.检查连接活跃度,不健康的连接则关闭。默认不检查,可以通过 druid.keepAlive 打开连接的健康检查。
- LogStatsThread
- 主要负责实施记录 Druid的开销情况,包括当前连接池数量、activeCount、这期间关闭的连接数量等等。LogStatsThread 按配置的timeBetweenLogStatsMillis 的周期时间,定期执行。如果 timeBetweenLogStatsMillis 没有配置,则该线程将不会被开启。
四、获取连接
-
当用户需要连接时,调用 dataSource的getConnection 方法。 DruidDataSource 首先需要判断当前线程池是否有可用的连接,如果有可用的连接则采用LRU策略获取,直接从 connections 尾部获取一个连接返回。尾部的连接是当前连接池中最活跃的连接,要么是刚刚释放回去的连接,要么就是新创建的。因此最大程度保证连接是可用的,健康的。
-
如果当前连接池没有可用的连接,那么首先会发一个信号告诉 CreateConnectionThread 去创建新的连接,然后将自己挂起直到有新的可用连接。新的连接会从两个地方获得,一个是 CreateConnectionThread,一个是刚刚回收的连接。
-
用户使用的Connection 是代理代理类 DruidConnectionHolder, 在执行 close 时不是直接将连接关闭,而是将连接回收。
回收的连接放在connects 末尾,存放完毕后发发一个 notEmpty 通知,通知正在等待连接的线程。
五、探活(连接池连接健康的检查机制)
- 为何要关注连接健康情况?
-
- Mysql 连接超时时间默认8 小时,超时以后原连接失效。如果继续使用原连接操作数据库,则应用程序会直接报连接不可用,这种对数据操作比较敏感的应用会有一些影响。这种连接不可用的场景非常常见,比如因为服务与数据库之间存在一些网络波动、数据库热迁移、mysql 内置超时机制等。比较小的影响就是某一次数据库操作不可用,但也有可能引起致命的问题,例如引起JDBC 内部的循环等待远程数据,导致 CPU 过高。
-
- 所以在生产开发时,需要关注连接的健康,如果连接不可用则废弃重新创建。
- 那么druid 如何做连接检查?
-
- Druid 提供了多个连接检查的时机点:获取连接时检查、归还连接时检查、空闲时检查
获取连接时检查:
就是每次拿到连接以后判断连接是否是可用的,如果可用则拿来使用,避免使用无效连接执行导致报错。如果连接不可用则直接销毁,重新获取。通过 testOnBorrow 参数来设置,默认是打开的
归还连接时检查:
每次连接使用完还给连接池前做一个健康检查,判断连接是否可用,不可用直接废弃。testOnReturn 设置归还时检查开关,默认是false
空闲时检查:
不是定时自动去检查,而是在调用时,如果连接空闲时间操作 timeBetweenEvictionRunsMillis 则检查一次,而不是每次检查一次。通过 testWhileIdle 参数设置,默认是 false
-
空闲时检查和获取连接时检查是互斥的,二者只能选择一个。归还时检查可以和其他两个组合使用,例如获取连接时先检查一遍,归还时再检查一遍,但一般生产上很少这样使用,毕竟每次检查都有性能开销。
-
就像每次做车检都需要有一些开销,连接检查也是。获取连接时检查和归还连接时检查都是对数据库操作都做一次校验,对于数据库变更频繁的系统是一笔很大的开销。所以一般会优先考虑空闲时检查机制,毕竟连接无效事件不是一个频繁事件。
-
- 另外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连接池核心原理(有源码图,感兴趣的同学可以去看看)