最近在某个项目的生产环境碰到一个数据库连接问题,使用的连接池是alibaba的druid_1.1.10,问题表现为:DBA监测到应用集群到oracle的连接数总会在半夜降低,并且大大低于每个节点druid配置的minIdle总和。
一开始怀疑此问题产生的原因是oracle侧主动关闭了连接,但很难去验证这个点,一方面是和DBA沟通起来比较麻烦,另一方面是没有确切的证据,纯粹靠猜想很难服众,所以退而求其次,尝试在druid连接池上去找原因。既然是半夜这种交易量小的时间点降低连接数,那么应该和druid对空闲连接的处理有关。
在github拉取了druid源码后,载入idea,使用minEvictableIdleTimeMillis进行了全局搜索,在结果列表中找到了一些可能与连接回收有关的类,最终定位到了DruidDataSource的内部类DestoryTask,简单的扫了一眼代码之后,基本就能确定DestroyTask是用于负责检测和销毁空闲连接的类了。
由于druid源码编译还得花时间研究,我直接搭建了一个简单的springboot工程,引入druid后对DruidDataSource的init()方法打断点,启动应用开始一步步调试...
DruidDataSource init时会启动一个销毁连接的线程,由于destoryScheduler为空,因此创建了DestroyConnectionThread线程去执行,如下图:
DestroyConnectionThread做的事情很简单,就是每隔固定的时间去执行一下DestoryTask的run方法,执行的间隔时间基于druid配置timeBetweenEvictionRunsMillis的值:
DestoryTask的run方法调用shrink方法,该方法是空闲连接检查的核心方法,至于removeAbandoned方法是用于回收借出去但一直未归还的连接(这种连接可能导致连接泄露),它与druid的配置removeAbandoned有关,这里就不细讲了:
shrink方法逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
|
根据调试过程中的源码分析,可知druid_1.1.10判断连接是否销毁还是保活的逻辑如下(只讨论checkTime为true的情况):
到这里,我们就可以下一个结论了:druid对于空闲连接还是有可能回收的,只要它未开启keepAlive并且闲置时间过长就会回收空闲连接,从而使得连接池中的连接数小于配置的minIdle值。
为了验证结论,我开启了druid monitor的web页面访问,然后在如下的页面中去观察池中连接的情况:
与druid空闲连接回收的相关参数配置如下图:
首先不开启keepAlive功能(druid也是默认关闭的),在应用启动的时候,从druid monitor中观察到连接池中的连接数如下:
等待大约2~3分钟之后(再此期间不要发起任何数据库请求),再次观察连接池中的连接数,可以发现连接数为0:
接着配置"spring.datasource.druid.keep-alive=true"以打开keepAlive,重启应用并重复上述过程,结果如下:
可以发现keepAlive起作用了,池中连接数维持在20,结论得到验证。接着回过头去查看了一下maxEvictableIdleTimeMillis这个参数的默认值为25200000,刚好7个小时,差不多能和DBA监测到的连接降低时间对上。
在解决问题的过程中,参考了官方文档以及他人在druid项目中提的issue,经历了怀疑问题、确认问题、解决问题三个阶段,不过个人在调试过程中仍然发现有如下问题:
(1)官方的配置文档中对属性minEvictableIdleTimeMillis做了如下描述:
然而实际上代码体现出来的逻辑并不是这么一回事,maxEvictableIdleTimeMillis更像起到了决定性的作用。
(2)timeBetweenEvictionRunsMillis、minEvictableIdleTimeMillis、maxEvictableIdleTimeMillis这三者设置的大小如果满足一定条件,也会导致keepAlive失效。根据源码,如果在某一轮扫描中(间隔时间timeBetweenEvictionRunsMillis),检测到连接的空闲时间小于minEvictableIdleTimeMillis,那么这些连接不需要keepAlive,自然也不会更新lastActiveTimeMillis,这里存在一个临界条件,使得连接空闲时间同时大于minEvictableIdleTimeMillis和maxEvictableIdleTimeMillis,这个临界条件触发的前提是:
1 2 3 |
|
下面是我的一个测试,druid相关配置情况如图:
启用应用并静静等待1~2分钟,通过druid monitor查看连接池状态:
通过浏览器调用一个http查询接口,连接池连接数恢复:
静静等待1~2分钟,可以看到连接池中的连接又被清空:
结论:虽然maxEvictableIdleTimeMillis这个参数我们一般不配置,它的默认值也比较大(7小时),但是实际在配置druid时,还是建议考虑keepAlive失效的因素,作为配置的一个考量。