druid连接池 - 长时间阻塞问题排查

前言

最近参与一个新项目,从老项目拷贝过来的代码,同样用的druid连接池 + PG。但是新项目却经常出现阻塞的情况,有时候甚至可以阻塞半个小时。一时间傻眼了。。

问题排查

问题背景

我们一共有6个开发同学,由于项目时间还是比较紧张的,所以大部分时间还是在开发,而不会发访问服务器。而当前阶段也还没有提测,没有其他同学的介入。而且每次第二天早上都会出现这种阻塞的情况。

请教DBA大佬

由于配置跟老项目一模一样,所以第一个反应是数据库出什么问题了?找DBA断断续续排查了好几天(太忙了,主要还是搞开发),最后发现数据库应该是正常的。但是有新发现

  • PG数据库的小版本不一样,新项目的要高一些
  • 新项目使用的JDK版本是java8,而老项目是java7

连接池

由于数据库排查结果是正常的,我只能找其他原因。于是我从tomcat的.out日志文件中重新找到被阻塞的线程的日志,找到Spring打印的DEBUG日志。发现确实是在获取数据库连接的时候被阻塞的。然后又梳理了一下应用的调用链路:

  • Spring -> ibatis -> druid -> PostgreSQL客户端 -> PostgreSQL服务器。

确定问题发生的地方只有两个地方了:要么是druid连接池,要么是PG客户端。

结合上一次两个项目的不同点,我顺着排查PG客户端,先升级PG客户端。发现问题依然存在。同时在PG的官方网站发现如果没有罕见情况,应当使用最新的java客户端

  • Current Version 42.3.1
    This is the current version of the driver. Unless you have unusual requirements (running old applications or JVMs), this is the driver you should be using. It supports PostgreSQL 8.2 or newer and requires Java 8 or newer. It contains support for SSL and the javax.sql package.

  • If you are using Java 8 or newer then you should use the JDBC 4.2 version.
    If you are using a Java version older than 6 then you will need to use a JDBC3 version of the driver, which will by necessity not be current, found in Other Versions.
    PostgreSQL JDBC 4.2 Driver, 42.3.1

至此,升级PG的java客户端无果。

Druid连接池

冷静下来,结合问题现象,每次第二天都会被阻塞,发现如下问题:

  1. 会不会是连接失效的原因?
  2. druid连接池不会移除失效连接?
  3. 是不是跟空闲连接有关,minIdle是多少?

顺着上面的问题,我找到了Druid连接池的连接销毁任务
druid连接池 - 长时间阻塞问题排查_第1张图片
druid连接池会保留最小的空闲连接数,并且销毁任务不会检查这些个连接是否有效,因此druid连接池会长期保留minIdle个连接。而在我们的场景下,这些连接肯定是都失效了的。

进一步看看,失效连接被出借的情况。上一次做druid连接池参数优化的时候,看了一下源码。在出借连接的时候还会做一次连接健康检查。
druid连接池 - 长时间阻塞问题排查_第2张图片
不管是testOnBorrow还是testWhileIdle都有可能做健康检查,不同的是testOnBorrow是一定会在出借连接时做检查,而testWhileIdle是有条件的。只有当连接空闲时间超过销毁任务的时间周期,才会被检查。默认是60s。

这也是网上大多推荐使用testWhileIdle的原因,因为连接空闲60s通常都还是可用的。但是这里有个坑就是,你不要装X去把这个timeBetweenEvictionRunsMillis时间调整更长,否则超时连接借出去了那就是你倒霉了。其实这个也算是druid的一个问题吧,因为同一个参数有好几个用途,牵一发而动全身,给耦合在一起了。

好了,我们应该是没有改这个配置,而在我们的场景下,一定会进入健康检查的,继续排查检查检查的代码。
druid连接池 - 长时间阻塞问题排查_第3张图片
发现这个健康检查是有设置超时时间的啊,奔溃啊。。然后发现个惊喜

protected volatile int validationQueryTimeout = -1;

翻译过来就是,按照不超时处理。这种情况下只能等待防火墙超时。。。

总论

于是大胆下结论:问题的元凶就是这个默认的健康检查超时时间,

  • 解决方案:只需要设置这个值为validationQueryTimeout = 2应该就可以了。可以在bean定义的时候使用property 标签赋值。注意哈,这个参数的时间单位是秒!
    这个超时时间也比较重要。例如,minIdle=10,超时时间为2s,那像我们这种业务场景意味着要20s后才能返回新的连接。因此要注意这两个参数的合理性

后记

其实我还没有用上面的方案试过,因为跟同事聊的时候,他说他们是改源码的方式修改超时时间的。。而那个时候,我就想改源码还是算了吧,项目路时间不允许啊。我使用的方案是,把minIdle设置为0,这样直接让销毁任务回收就行了。虽然也能解决问题,但确实不太好。下周找时间试试新方案。
设置超时时间的方案验证失败!原因待排查。
但直接把minIdle=0是可以解决问题的!

你可能感兴趣的:(postgresql,database,连接池)