Druid 数据连接健康检查机制

昨天情人节, CPU 也激动了, 导致某应用CPU 出现飙高,经过定位后是数据库连接失效导致JDBC 底层死循环问题。。借此机会看了下druid中连接检查的机制,本文谈谈druid 如何做连接检查。

Mysql 连接超时时间默认8 小时,超时以后原连接失效。如果继续使用原连接操作数据库,则应用程序会直接报连接不可用,这种对数据操作比较敏感的应用会有一些影响。这种连接不可用的场景非常常见,比如因为服务与数据库之间存在一些网络波动、数据库热迁移、mysql 内置超时机制等。比较小的影响就是某一次数据库操作不可用,但也有可能引起致命的问题,例如引起JDBC 内部的循环等待远程数据,导致 CPU 过高。

所以在生产开发时,需要关注连接的健康,如果连接不可用则废弃重新创建。本文着重介绍 Druid 数据源连接检查机制,以此明晰日常开发过程中如何配置参数以避免连接不可用导致的灾难。

Druid 连接管理的整体流程

首先用一张图回顾下 Druid 整体执行流程,感兴趣的读者可以阅读我之前写的的文章: Druid系列(2)--连接池原理

  • 首先从连接池中connections 里判断是否有可用的连接,如果没有则创建一个新的连接存放到 连接池,并从连接池获取。

  • 连接执行完成以后,回收保持到连接池中。

  • CreateConnectionTask 负责创建新的连接,使得连接数量 > minIdle 数量。

  • DetroyTask 定时回收线程,将空闲连接回收,比如超过了 最大空闲时间的连接。

连接健康检查的时机

结合上面的执行流程图,Druid 提供了多个连接检查的时机点: 获取连接时检查 归还连接时检查空闲时检查。

获取连接时检查 

就是每次拿到连接以后判断连接是否是可用的,如果可用则拿来使用,避免使用无效连接执行导致报错。如果连接不可用则直接销毁,重新获取。就像自驾租借车一样,开之前需要检查是否可用才敢开。这个通过 testOnBorrow 参数来设置,默认是打开的(borrow 这个词也体现租用的概念,有借就要有还)。

归还连接时检查

每次连接使用完还给连接池前做一个健康检查,判断连接是否可用,不可用直接废弃。还是租车自驾的例子,归还车的时候租车公司都得检查下车看有没有把车给弄坏了。testOnReturn 设置归还时检查开关,默认是false。

空闲时检查

就像租车公司定期要给车做一个检查,如年检等,就为了保证车可用。在Druid 类似,但是不同的时不是定时自动去检查,而是在借用时如果连接空闲时间操作 timeBetweenEvictionRunsMillis 则检查一次,而不是每次检查一次。通过 testWhileIdle 参数设置,默认是 false。 

为什么不用像车检一样定时每年检查一次?我理解这边是为了提高性能考虑,如果定期检查有的连接检查了未必被使用到就被销毁了,做了一次无用操作;另外就是检查耗资源,还需考虑定时器和连接获取之间的并发问题。

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

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

如果 validationQuery 为空则不校验。校验时直接使用当前连接执行 validationQuery 的语句,设置socketTimeout 时间为 validationQueryTimeout。如果 validationQueryTimeout 未设置,或置为 <0, 那么超时时间则不设置。

健康检查机制

druid 提供了两种健康检查机制:

  • validationQuery 查询机制。通过配置 validationQuery sql 语句,检查连接时执行 validationQuery 语句看是否正常,一般配置为 select 'x'。 

  • ValidConnectionChecker 机制 。高版本支持定义模式,默认情况下 druid 会为不同的 JDBC 设置 checker。MySql 的默认ValidConnectionChecker 是以 ping 方式处理。

validationQuery 查询机制

低版本的 druid 默认只支持该模式,检查时就在原来的连接执行一次 validationQuery 配置的sql 语句,来校验连接是否正常。这种机制下 validationQuery 是必须配置的,如果 validationQuery 没有配置,即使 testOnBorrow、testOnReturn、testWhileIdle 三个开关打开都不能生效。

validationQuery 支持超时时间 validationQueryTimeout,如果没有设置超时时间则无限等待直到失败,因此建议配置 validationQueryTimeout。

这种和我们以往理解的连接检查机制不同,系统间一般通过 ping 来判断连接健康性。ping 只要通过很少的网络传输就可以完成连接验证。而这种方式不仅需要传输validationQuery 语句,还需在数据库层面执行 sql 语句,注定比 ping 模式性能低。低版本的 druid 只支持该模式,也被很多人所吐槽。

ValidConnectionChecker 机制

在高版本里引入了 ValidConnectionChecker。ValidConnectionChecker 可以为不同的数据库类型定制不同的 检查方式,同时支持自定义的连接健康检查方式。在 高版本下默认使用该模式 ,如果没有设置或解析失败,则退回到 validationQuery。druid 启动时根据配置的数据源自动配置对应的 ValidConnectionChecker。

如果是自定义检查器,实现ValidConnectionChecker 接口,并在初始化DruidDataSource时设置即可:

MySqlValidConnectionChecker

日常使用 mysql 比较多,这里带大家看下 mysql 下的健康检查机制是什么。  MySqlValidConnectionChecker 是mysql 的连接默认检查器,即数据库配置的连接驱动器为 com.mysql.jdbc.Driver 。

从 MySqlValidConnectionChecker 构造器源码看,主要逻辑是 判断JDBC是否支持 pingInternal 方式,如果支持则使用 ping, 否则退回使用 validationQuery 。MySqlValidConnectionChecker 依赖高版本的 JDBC, 即 JDBC 5.1.38, 如果低版本启动则会报以下错误:

使用 ping 方式校验时就是执行 pingInternal 方法,与 validationQuery 可以配置超时时间 validationQueryTimeout,默认 1s。

相关配置

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章