记一次 Druid 超时配置的问题 → 引发对 Druid 时间配置项的探究
开心一刻
一天在路边看到一个街头采访
记者:请问,假如你儿子娶媳妇,给多少彩礼合适呢
大爷:一百万吧,再给一套房,一辆车
大爷沉思一下,继续说到:如果有能力的话再给老丈人配一辆车,毕竟他把女儿养这么大也不容易
记者:那你儿子多大了?
大爷:我没有儿子,有两个女儿

问题背景
最近生产环境出现了一个问题,错误日志类似如下
Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 1010, active 10, maxActive 10, creating 0, runningSqlCount 10 : select * from tbl_user
日志信息提示的很明显:获取 JDBC Connection 失败,因为从 druid 连接池获取 connection 超时了

上图的意思是:执行 select * from tbl_user 之前,需要从 druid 连接池中获取一个 connect
而此时连接池的状态是:一共 10 个激活的 connect ,连接池最大创建 10 个 connect ,正在执行 sql 的 connect 也是 10 个
所以不能创建新的 connect ,那就等呗,一共等了 1010 毫秒,还是拿不到 connect ,就抛出 GetConnectionTimeoutException 异常
简单点说就是是连接池中连接数不够,在规定的时间内拿不到 connect
那有人就说了:连接池的最大数量设置大一点,问题不就解决了吗
最大连接数设置大一点只能说可以降低问题发生的概率,不能完全杜绝,因为网络情况、硬件资源的使用情况等等都是不稳定因素
今天要讲的不是连接池大小问题,而是超时设置问题,我们慢慢往下看
问题复现
我们先来模拟下上述问题
MySQL 版本: 5.7.21 ,隔离级别:RR
Druid 版本: 1.1.12
spring-jdbc 版本: 5.2.3.RELEASE
DruidDataSource 初始化
为了方便演示,就手动初始化了

多线程查询
线程数多于连接池中 connect 数

模拟慢查询
如果查询飞快,15 个查询,可能都用不上 10 个 connect ,所以我们需要简单处理下
很简单,给表加写锁呗: LOCK TABLES tbl_user WRITE
给表 tbl_user 加上写锁,然后跑线程去查询 tbl_user 的数据
异常演示
先锁表,再启动程序

可以看到,15 个线程中,有 5 个线程获取 connect 失败
Thread-13 Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 10004, active 10, maxActive 10, creating 0, runningSqlCount 10 : select * from tbl_user
Thread-5 Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 10004, active 10, maxActive 10, creating 0, runningSqlCount 10 : select * from tbl_user
Thread-10 Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 10004, active 10, maxActive 10, creating 0, runningSqlCount 10 : select * from tbl_user
Thread-7 Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 10004, active 10, maxActive 10, creating 0, runningSqlCount 10 : select * from tbl_user
Thread-8 Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 10004, active 10, maxActive 10, creating 0, runningSqlCount 10 : select * from tbl_user
示例代码:druid-timeout
时间配置项
Druid 中关于时间的配置项有很多,我们我们重点来看下如下几个
maxWait
最大等待时长,单位是毫秒,-1 表示无限制
从连接池获取 connect ,如果有空闲的 connect ,则直接获取到,如果没有则最长等待 maxWait 毫秒,如果还获取不到,则抛出 GetConnectionTimeoutException 异常
removeAbandonedTimeout
设置 druid 强制回收连接的时限,单位是秒
从连接池获取到 connect 开始算起,超过此值后, Druid 将强制回收该连接
官网也有说明:连接泄漏监测
validationQueryTimeout
检测连接是否有效的超时时间,单位是秒,-1 表示无限制
Druid 内部的一个检测 connect 是否有效的超时时间,需要结合 validationQuery 来配置
timeBetweenEvictionRunsMillis
检查空闲连接的频率,单位是毫秒, 非正整数表示不进行检查
空闲连接检查的间隔时间, Druid 池中的 connect 数量是一个动态从 minIdle 到 maxActive 扩张与收缩的过程
connect 使用高峰期,数量会从 minIdle 扩张到 maxActive ,使用低峰期, connect 数量会从 maxActive 收缩到 minIdle
收缩的过程会回收一些空闲的 connect ,而 timeBetweenEvictionRunsMillis 就是检查空闲连接的间隔时间
queryTimeout
执行查询的超时时间,单位是秒,-1 表示无限制
最终会应用到 Statement 对象上,执行时如果超过此时间,则抛出 SQLException
transactionQueryTimeout
执行一个事务的超时时间,单位是秒
minEvictableIdleTimeMillis
最小空闲时间,单位是毫秒,默认 30 分钟
如果连接池中非运行中的连接数大于 minIdle ,并且某些连接的非运行时间大于 minEvictableIdleTimeMillis ,则连接池会将这部分连接设置成 Idle 状态并关闭
maxEvictableIdleTimeMillis
最大空闲时间,单位是毫秒,默认 7 小时
如果 minIdle 设置的比较大,连接池中的空闲连接数一直没有超过 minIdle ,那么那些空闲连接是不是一直不用关闭?
当然不是,如果连接太久没用,数据库也会把它关闭(MySQL 默认 8 小时),这时如果连接池不把这条连接关闭,程序就会拿到一条已经被数据库关闭的连接
为了避免这种情况, Druid 会判断池中的连接,如果非运行时间大于 maxEvictableIdleTimeMillis ,也会强行把它关闭,而不用判断空闲连接数是否小于 minIdle
再看问题
其实前面的示例中设置了

获取 connect 的最大等待时长是 10000 毫秒,也就是 10 秒
而 removeAbandonedTimeout 设置是 7 秒
照理来说 connect 如果 7 秒未执行完 SQL 查询,就会被 Druid 强制回收进连接池,那么等待 10 秒应该能够获取到 connect ,为什么会抛出 GetConnectionTimeoutException 异常了?
这也就是文章标题中的超时设置问题
源码探究
很显然,我们从 dataSource.init(); 开始跟源码
会看到如下一块代码

我们继续跟 createAndStartDestroyThread();

重点来了,我们看下 DestroyTask 到底是怎么样一个逻辑

我们接着跟进 removeAbandoned ,关键代码

如果 connect 正在运行中是不会被强制回收进连接池的
回到我们的示例,connect 都是在运行中,只是都在进行慢查询,所以是无法被强制回收进连接池的,那么其他线程自然在 maxWait 时间内无法获取到 connect
至此文章标题中的问题的原因就找到了
那么问题又来了: removeAbandonedTimeout 作用在哪?
我们再仔细阅读下:连接泄漏监测
Druid 提供了 RemoveAbandanded 相关配置,目的是监测连接泄露,回收那些长时间游离在连接池之外的空闲 connect
可能因为程序问题,导致申请的 connect 在处理完 sql 查询后,不能回到连接池的怀抱,那么这个 connect 处理游离态,它真实存在,但后续谁也申请不到它,这就是连接泄露
而 removeAbandoned 的设计就是为了帮助这些泄露的 connect 回到连接池的怀抱
解决问题
开启 removeAbandoned 对性能有影响,官方不建议在生产环境使用
那么我们接受官方的建议,不开启 removeAbandoned (不配置即可,默认是关闭的)
为了不让慢查询占用整个连接池,而拖垮整个应用,我们设置查询超时时间 queryTimeout
有两种方式,一个是设置 DataSource 的 queryTimeout ,另一个是设置 JdbcTemplate 的 queryTimeout
如果两个都设置,最终生效的是哪个,为什么?大家自己去分析,权当是给大家留个一个作业
这里就配置 DataSource 的 queryTimeout ,给大家演示下效果

可以看到,所有线程都获取到了 connect
总结
1、 Druid 的 removeAbandoned 对性能有影响,不建议开启
removeAbandoned 的开启后的作用要捋清楚,而非简单的过期强制回收
2、 Druid 的时间配置项有很多,不局限于文中所讲,但常用的就那么几个,其他的保持默认值就好
配置的时候一定要弄清楚各个配置项的具体作业,不要去猜!
3、查询超时 queryTimeout 即可在 DataSource 配置,也可在 JdbcTemplate 配置
记一次 Druid 超时配置的问题 → 引发对 Druid 时间配置项的探究的更多相关文章
- Druid 查询超时配置的探究 → DataSource 和 JdbcTemplate 的 queryTimeout 到底谁生效?
开心一刻 昨晚跟我妈语音 妈:我年纪有点大了,想抱孩子了 我:妈,我都多大了,你还想抱我? 妈:我想抱小孩,谁乐意抱你呀! 我:刚好小区有人想找月嫂,要不我帮你联系下? 妈:你给我滚 然后她直接把语音 ...
- Spring Boot Druid数据源配置
package com.hgvip.config; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.su ...
- SpringBoot(七):集成DataSource 与 Druid监控配置
绑定DataSource:Spring Boot默认的数据源是:org.apache.tomcat.jdbc.pool.DataSource,Druid是Java语言中最好的数据库连接池,并且能够提供 ...
- druid监控配置
druid相对于传统的c3p0和dbcp及其dbcp2等多个很多新特性 可以在线监控数据库及其表和sql以及Controller的requestMapping和对应的业务方面请求和session等 是 ...
- 数据库连接池druid 监控台配置
这篇文章主要讲druid 监控台的配置及界面使用介绍. 业界把 Druid 和 HikariCP 做对比后,虽说 HikariCP 的性能比 Druid 高,但是因为 Druid 包括很多维度的统计和 ...
- 阿里druid数据源配置及数据库密码加密
注意: 1.阿里默认只对用户密码解密 2.druid 1.0.16版本及以上的解密时需要同时配置publicKey 一.生成密文密码 1 前提:已经配置了jdk环境 1.生成密文密码需要准备druid ...
- Druid基本配置
最近公司要用Druid 所以看了下基本配置及配置过程中出现的问题 Druid是什么? Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0.DBCP.PROXOOL等DB池的优点,同时 ...
- druid 参数配置详解
druid 参数配置详解 */--> druid 参数配置详解 Table of Contents 1. 初始化连接 2. 参数配置及说明 3. 注意事项 3.1. 底层连接 3.2. 空闲检查 ...
- springboot+druid+mybatis-Plus 配置详解
网上找了很多关于springboot+druid+mybatis-Plus的配置,遇见的很多问题 也没找到好的解决方案.折腾了好几天终于自己配置通过了. springboot的pom文件 <pa ...
随机推荐
- redis:缓存穿透、缓存击穿、缓存雪崩
缓存穿透的解决方案(空标记) 缓存穿透是指,在数据存储系统中不存在的记录,不会被存储到缓存中.这种记录每次的查询流量都会穿透到数据存储层.在高流量的场景下,不断查询空结果会大量消耗数据查询服务的资源, ...
- XCTF练习题---MISC---Hidden-Message
XCTF练习题---MISC---Hidden-Message flag:Heisenberg 解题步骤: 1.观察题目,下载附件 2.拿到手以后发现是一个数据包格式,打开看一下 3.查看UDP流,并 ...
- 【ConcurrentHashMap】浅析ConcurrentHashMap的构造方法及put方法(JDK1.7)
目录 引言 代码讲解 构造方法 put方法 ensureSegment Segment.put 引言 ConcurrentHashMap的数据结构如下. 和HashMap的最大区别在于多了一层Segm ...
- .NET性能优化-为结构体数组使用StructLinq
前言 本系列的主要目的是告诉大家在遇到性能问题时,有哪些方案可以去优化:并不是要求大家一开始就使用这些方案来提升性能. 在之前几篇文章中,有很多网友就有一些非此即彼的观念,在实际中,处处都是开发效率和 ...
- 通过有序线性结构构造AVL树
通过有序线性结构构造AVL树 本博客旨在结局利用有序数组和有序链表构造平衡二叉树(下文使用AVL树代指)问题. 直接通过旋转来构造AVL树似乎是一个不错的选择,但是稍加分析就会发现,这样平白无故做了许 ...
- 846. Hand of Straights - LeetCode
Question 846. Hand of Straights Solution 题目大意:打牌,判断牌是否能全部按顺子出 思路:构造一个list,存储1,2,3,4,5,6,7,8并排序,构造一个m ...
- cefsharp + winform 内嵌网页的触屏输入焦点问题
原文 现象 我正在使用 cefsharp + winform 建立一个桌面程序用于显示网页.但程序启动后触屏点击网页中的输入框,使用键盘输入,文字输入不进去.win + D 最小化程序后,再恢复窗口才 ...
- 解锁!玩转 HelloGitHub 的新姿势
本文不会涉及太多技术细节和源码,请放心食用 大家好,我是 HelloGitHub 的老荀,好久不见啊! 我在完成 HelloZooKeeper 系列之后,就很少"露面了".但是我对 ...
- Groovy基础语法
Groovy 基础语法 变量定义 1.支持动态类型,使用def关键字定义变量 // Java中定义变量的方式 int age = 18; String name = "张三"; / ...
- 快速 IO
IO 的进化史 cin和cout 刚开始学的时候,老师叫我们用 cin 和 cout 大概是因为这最简单吧 cin>>x; cout<<x scanf和printf 学到函数了 ...