记一次 Druid 超时配置的问题,引发对 Druid 时间配置项的探究

开心一刻

一天在路边看到一个街头采访

记者:请问,假如你儿子娶媳妇,给多少彩礼合适呢

大爷:一百万吧,再给一套房,一辆车

大爷沉思一下,继续说到:如果有能力的话再给老丈人配一辆车,毕竟他把女儿养这么大也不容易

记者:那你儿子多大了?

大爷:我没有儿子,有两个女儿

fc3f12a5e34466d5457af5de996583d8.gif

问题背景

最近生产环境出现了一个问题,错误日志类似如下:

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 超时了。

 5a98a301618a5554c53523247360f5cc.png

上图的意思是:执行 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 初始化

为了方便演示,就手动初始化了:

e939739c9e9b1533d77e64194e65d7cd.png


多线程查询

线程数多于连接池中 connect 数。

48e0f214afffcf50e1112d91a88aceb2.png


模拟慢查询

如果查询飞快,15 个查询可能都用不上 10 个 connect ,所以我们需要简单处理下。

很简单,给表加写锁:

LOCK TABLES tbl_user WRITE

给表 tbl_user 加上写锁,然后跑线程去查询 tbl_user 的数据。


异常演示

先锁表,再启动程序

f162cd9ccf5b8e0a3776b3837d8fb3fd.gif

可以看到,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
https://gitee.com/youzhibing/qsl-project/tree/master/druid-timeout

时间配置项

Druid 中关于时间的配置项有很多,我们我们重点来看下如下几个:

maxWait

最大等待时长,单位是毫秒,-1 表示无限制。

  • 从连接池获取 connect;

  • 如果有空闲的 connect ,则直接获取到;

  • 如果没有则最长等待 maxWait 毫秒;

  • 如果还获取不到,则抛出 GetConnectionTimeoutException 异常。

removeAbandonedTimeout

设置 Druid 强制回收连接的时限,单位是秒。

从连接池获取到 connect 开始算起,超过此值后, Druid 将强制回收该连接。

官网也有说明:连接泄漏监测。

https://github.com/alibaba/druid/wiki/%E8%BF%9E%E6%8E%A5%E6%B3%84%E6%BC%8F%E7%9B%91%E6%B5%8B

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 。

再看问题

其实前面的示例中设置了。

37f169ba5f5b27bc63f5d523c6bea700.png

获取 connect 的最大等待时长是 10000 毫秒,也就是 10 秒。

而 removeAbandonedTimeout 设置是 7 秒。

照理来说 connect 如果 7 秒未执行完 SQL 查询,就会被 Druid 强制回收进连接池,那么等待 10 秒应该能够获取到 connect 。

为什么会抛出 GetConnectionTimeoutException 异常了?

这也就是文章标题中的超时设置问题。

源码探究

很显然,我们从 dataSource.init(); 开始跟源码,会看到如下一段代码:

55bdb4545de03efb6b92b213c0b9df34.png

我们继续跟 createAndStartDestroyThread():

bb3c1f6f3aebfa8153121560984ef61a.png

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

a858d1355203b7687f5d5ad70390ad89.png

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

682686b161353371a1cadf1f200af66d.png

如果 connect 正在运行中是不会被强制回收进连接池的。

回到我们的示例:connect 都是在运行中,只是都在进行慢查询,所以是无法被强制回收进连接池的,那么其他线程自然在 maxWait 时间内无法获取到 connect 。

至此,文章标题中的问题的原因就找到了。

那么问题又来了:removeAbandonedTimeout 作用在哪?

我们再仔细阅读下:连接泄漏监测

https://github.com/alibaba/druid/wiki/%E8%BF%9E%E6%8E%A5%E6%B3%84%E6%BC%8F%E7%9B%91%E6%B5%8B

Druid 提供了 RemoveAbandanded 相关配置,目的是监测连接泄露,回收那些长时间游离在连接池之外的空闲 connect。

可能因为程序问题,导致申请的 connect 在处理完 SQL 查询后,不能回到连接池的怀抱。那么这个 connect 处理游离态,它真实存在,但后续谁也申请不到它,这就是连接泄露。

而 removeAbandoned 的设计就是为了帮助这些泄露的 connect 回到连接池的怀抱。

解决问题

开启 removeAbandoned 对性能有影响,官方不建议在生产环境使用

那么我们接受官方的建议,不开启 removeAbandoned (不配置即可,默认是关闭的)。

为了不让慢查询占用整个连接池,而拖垮整个应用,我们设置查询超时时间 queryTimeout 。

有两种方式,一个是设置 DataSource 的 queryTimeout ,另一个是设置 JdbcTemplate 的 queryTimeout 。

如果两个都设置,最终生效的是哪个,为什么?大家自己去分析,权当是给大家留个一个作业。

这里就配置 DataSource 的 queryTimeout ,给大家演示下效果:

ae57c8964d8e1643bbaf9c8e7ba7af1a.gif

可以看到,所有线程都获取到了 connect。

总结

  • Druid 的 removeAbandoned 对性能有影响,不建议开启。removeAbandoned 的开启后的作用要捋清楚,而非简单的过期强制回收;

  • Druid 的时间配置项有很多,不局限于文中所讲,但常用的就那么几个,其他的保持默认值就好。配置的时候一定要弄清楚各个配置项的具体作业,不要去猜!

  • 查询超时 queryTimeout 即可在 DataSource 配置,也可在 JdbcTemplate 配置。

转自:青石路,

链接:cnblogs.com/youzhibing/p/16458860.html

推荐阅读:
世界的真实格局分析,地球人类社会底层运行原理
不是你需要中台,而是一名合格的架构师(附各大厂中台建设PPT)

企业IT技术架构规划方案

论数字化转型——转什么,如何转?

华为干部与人才发展手册(附PPT)

企业10大管理流程图,数字化转型从业者必备!

【中台实践】华为大数据中台架构分享.pdf

华为的数字化转型方法论

华为如何实施数字化转型(附PPT)

超详细280页Docker实战文档!开放下载

华为大数据解决方案(PPT)
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Druid连接池是一个开源的、高效的数据库连接池,适用于Java应用程序。它能够提供连接池和管理连接的功能,帮助进行数据库连接的管理和优化。 Druid连接池的配置主要包括以下几个方面: 1. 数据源配置:可以通过配置文件或代码来设置数据库相关的属性,如驱动类型、URL、用户名、密码等。这些配置可以根据具体的数据库类型和环境需求来设置。 2. 连接池参数配置:可以设置连接池的一些基本参数,如初始化连接数、最小连接数、最大连接数等。这些参数会直接影响连接池的性能和资源占用情况,需要根据具体应用的需求进行调整。 3. 连接属性配置:可以设置每个连接的一些属性,如连接超时时间、最大等待时间、是否自动提交等。这些属性可以根据具体需求进行设置,以满足应用程序的要求。 4. 监控配置:可以设置连接池的监控功能,包括连接池的活跃连接数、空闲连接数、执行SQL次数、慢查询次数等。这些监控数据可以通过配置输出到日志文件或通过JMX暴露出来,以便进行监控和调优。 5. 连接池扩展配置:可以通过配置来设置连接池的一些扩展功能,如连接池的预处理、过滤器等。这些功能可以提供更灵活的连接管理方式,以满足特定需求。 通过合理配置Druid连接池,可以提高应用程序对数据库连接的管理和利用效率,减少连接泄露和性能问题。但是需要注意的是,配置应遵循最佳实践,并根据实际情况进行调整和优化,以达到最佳的数据库连接池配置效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值