druid keepAlive bug踩坑

背景

7月28日晚,公司服务数据库降配重启,重启过程在10s之内,但是却发现Communications link failure异常却一直在持续,过了接近2小时还没有完全消失,初步断定是连接池中的无用连接没有被清理,于是通过重启应用服务重置数据连接,报错消失

讨论

有同事表示为数据库的validation-query添加select 1属性,就可以解决连接失效但是没有被回收的问题,该属性是设置链接检查异常语句,当请求数据库的时候会通过该语句检查链接的可用性。但出于好奇心我debug了下代码,最后发现事情并没有这么简单.

研究

先抛结论

  1. druid和hikari默认配置都会检测连接的有效性,不过默认策略不同
  2. 不论是druid还是hikari,在默认配置+mysql的情况下使用的都是jdbc自带的ping方式请求数据库,单纯配validation-query在mysql下不会生效
  3. druid默认只有线程空闲时间大于timeBetweenEvictionRunsMillis时并且新请求使用,才会ping数据库,检查连接,如果连接失效,则抛弃该连接,并自动重试。或者被DestroyTask(需要配置)扫描到,连接也会被抛弃。
  4. hikari在该连接请求间隔大于500ms的时候会去ping数据库,若失败则新建连接重试(有超时时间),由于500ms比较短,所以理论上使用hikari连接池的服务不太会因为数据库重启导致大量Communications link failure,除非启动时间太长超过hikari的重试时间或者并发较大。所以运维说别的服务没遇见过这个问题,我合理推测是这个原因

源码

druid校验请求有效性
com.alibaba.druid.pool.DruidDataSource#getConnectionDirect

在这里插入图片描述

druid可以配置三处校验连接,分别是“获取连接时”,“连接空闲时”,“连接归还时”。其对应的配置是testOnBorrow、testWhileIdle、testOnReturn。1.1.21版本默认是testWhileIdle。
当testWhileIdle为true时,会对空闲超过timeBetweenEvictionRunsMillis(默认1分钟)的线程,在下次请求时,先进行连接校验,失败则抛弃连接并重试;如果在timeBetweenEvictionRunsMillis内请求(被保活任务校验通过,也算被请求,这个很重要),则不会校验连接的可用性。因此,假如连接在timeBetweenEvictionRunsMillis内失效,并且有请求使用了该连接,则请求会直接失败,连接被抛弃,并抛出异常
在这里插入图片描述
注意上图lastActiveTimeMills的取值逻辑,它会在lastActiveTimeMills和lastKeepTimeMills中取一个较大值。

下面展示下druid校验连接的代码
在这里插入图片描述
com.alibaba.druid.pool.vendor.MySqlValidConnectionChecker
在这里插入图片描述
在这里插入图片描述
可以看到mysql中usePingMethod变量决定了是用ping还是自定义的validationQuery,那么usePingMethod是哪里来的捏,看下面该MySqlValidConnectionChecker的构造函数。

usePingMethod是否为true取决于使用的jdbc版本是否支持ping。实测用的是com.mysql.cj.jdbc.ConnectionImpl,它是有pingInternal方法的,因此该值为true
在这里插入图片描述

hikari检测连接有效性

com.zaxxer.hikari.pool.HikariPool#getConnection
在连接空闲超过500ms(默认)的时候,就会ping一下数据库
在这里插入图片描述
如果isUseJdbc4Validation为true则通过jdbc驱动自带的ping方法校验连接可用性,否则使用connectiontestQuery
在这里插入图片描述
com.mysql.cj.jdbc.ConnectionImpl#isValid
在这里插入图片描述
那么hikari怎么让isUseJdbc4Validation为false呢,观其源码,答案是设置hiari的connectinTestQuery
在这里插入图片描述

druid的bug

那么问题来了,到底是什么导致了线上废弃连接一直没有被回收呢?

答案是druid的线程保活任务对连接校验时忽略了非SQLException异常,且默认校验boolean值为true
该问题存在于1.2.6以下版本的druid,并且在druid的github上可以查到该issue
https://github.com/alibaba/druid/issue/4227
下图红框部分可以清楚地看到,假如数据库主动断开连接,抛出IO异常或者socket异常,则会被isValidConnection直接跑出来,并且被catch块吞掉,此时result变量还是为true,即校验成功。
在这里插入图片描述
在这里插入图片描述
一旦保活任务校验连接成功,则会刷新当前连接的lastKeepTime,由于lastKeepTime被刷新,所以testWhileIdle也没有生效,导致连接直到被请求使用抛出异常了,才被丢弃
在这里插入图片描述
在升级版本到1.2.6以上后,可恢复这个问题~~

  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值