HikariCP 源码分析之 leakDetectionThreshold 及实战解决 Spark/Scala 连接池泄漏
摘要: 原创出处 https://mp.weixin.qq.com/s/_ghOnuwbLHOkqGKgzWdLVw 「渣渣王子」欢迎转载,保留摘要,谢谢!
- 1. 概念
-
2. 源码分析
- 2.1.1 HikariConfig
- 2.1.2 HouseKeeper
- 2.1.3 小结
- 2.2.1 getConnection
- 2.2.2 leakTaskFactory、ProxyLeakTaskFactory、ProxyLeakTask
- 2.2.3 close
- 3. 测试模拟
- 4. Spark/Scala连接池泄漏问题排查
- 5. 参考资料
- 6. 系列文章
概念
此属性控制在记录消息之前连接可能离开池的时间量,单位毫秒,默认为0,表明可能存在连接泄漏。 如果大于0且不是单元测试,则进一步判断:(leakDetectionThreshold < SECONDS.toMillis(2) or (leakDetectionThreshold > maxLifetime && maxLifetime > 0),会被重置为0。即如果要生效则必须>0,而且不能小于2秒,而且当maxLifetime > 0时不能大于maxLifetime(默认值1800000毫秒=30分钟)。
leakDetectionThreshold This property controls the amount of time that a connection can be out of the pool before a message is logged indicating a possible connection leak. A value of 0 means leak detection is disabled. Lowest acceptable value for enabling leak detection is 2000 (2 seconds). Default: 0
更多配置大纲详见文章 【追光者系列】HikariCP默认配置
源码解析
我们首先来看一下leakDetectionThreshold用在了哪里的纲要图:
Write
还记得上一篇文章 【追光者系列】HikariCP源码分析之从validationTimeout来讲讲Hikari 2.7.5版本的那些故事 提到:我们可以看到在两处看到validationTimeout的写入,一处是PoolBase构造函数,另一处是HouseKeeper线程。 leakDetectionThreshold的用法可以说是异曲同工,除了构造函数之外,也用了HouseKeeper线程去处理。
HikariConfig
在com.zaxxer.hikari.HikariConfig中进行了leakDetectionThreshold初始化工作,
validateNumerics方法中则是解释了上文及官方文档中该值validate的策略
该方法会被HikariConfig#validate所调用,而HikariConfig#validate会在HikariDataSource的specified configuration的构造函数使用到
也在每次getConnection的时候用到了,
这里要特别提一下一个很牛逼的Double-checked_locking的实现,大家可以看一下这篇文章 https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
HouseKeeper
我们再来看一下com.zaxxer.hikari.pool.HikariPool这个代码,该线程尝试在池中维护的最小空闲连接数,并不断刷新的通过MBean调整的connectionTimeout和validationTimeout等值,leakDetectionThreshold这个值也是通过这个HouseKeeper的leakTask.updateLeakDetectionThreshold(config.getLeakDetectionThreshold())去管理的。
这里补充说一下这个HouseKeeper,它是在com.zaxxer.hikari.pool.HikariPool的构造函数中初始化的:this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();
这里简要说明一下,ScheduledThreadPoolExecutor是ThreadPoolExecutor类的子类,因为继承了ThreadPoolExecutor类所有的特性。但是,Java推荐仅在开发定时任务程序时采用ScheduledThreadPoolExecutor类。 在调用shutdown()方法而仍有待处理的任务需要执行时,可以配置ScheduledThreadPoolExecutor的行为。默认的行为是不论执行器是否结束,待处理的任务仍将被执行。但是,通过调用ScheduledThreadPoolExecutor类的setExecuteExistingDelayedTasksAfterShutdownPolicy()方法则可以改变这个行为。 传递false参数给这个方法,执行shutdown()方法之后,待处理的任务将不会被执行。 取消任务后,判断是否需要从阻塞队列中移除任务。其中removeOnCancel参数通过setRemoveOnCancelPolicy()设置。之所以要在取消任务后移除阻塞队列中任务, 是为了防止队列中积压大量已被取消的任务 。 从这两个参数配置大家可以了解到作者的对于HouseKeeper的配置初衷。
小结
Hikari通过构造函数和HouseKeeper对于一些配置参数进行初始化及动态赋值,动态赋值依赖于HikariConfigMXbean以及使用任务调度线程池ScheduledThreadPoolExecutor来不断刷新配置的。
我们仅仅以com.zaxxer.hikari.HikariConfig来做下小结,允许在运行时进行动态修改的主要有:
不允许在运行时进行改变的主要有
Read
getConnection
在com.zaxxer.hikari.pool.HikariPool的核心方法getConnection返回的时候调用了poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now) 注意,创建代理连接的时候关联了ProxyLeakTask。 连接泄漏检测的原理就是: 连接有借有还,hikari是每借用一个connection则会创建一个延时的定时任务,在归还或者出异常的或者用户手动调用evictConnection的时候cancel掉这个task
leakTaskFactory、ProxyLeakTaskFactory、ProxyLeakTask
在HikariPool构造函数里,初始化了leakTaskFactory,以及houseKeepingExecutorService。
com.zaxxer.hikari.pool.ProxyLeakTaskFactory是作者惯用的设计,我们看一下源码:
如果leakDetectionThreshold=0,即禁用连接泄露检测,schedule返回的是ProxyLeakTask.NO_LEAK,否则则新建一个ProxyLeakTask,在leakDetectionThreshold时间后触发
再看一下com.zaxxer.hikari.pool.ProxyLeakTask的源码
NO_LEAK类里头的方法都是空操作 一旦该task被触发,则抛出Exception("Apparent connection leak detected")
我们想起了什么,是不是想起了 【追光者系列】HikariCP源码分析之allowPoolSuspension 那篇文章里有着一摸一样的设计?
isAllowPoolSuspension默认值是false的,构造函数直接会创建SuspendResumeLock.FAUX_LOCK;只有isAllowPoolSuspension为true时,才会真正创建SuspendResumeLock。
com.zaxxer.hikari.util.SuspendResumeLock内部实现了一虚一实两个java.util.concurrent.Semaphore
由于Hikari的isAllowPoolSuspension默认值是false的,FAUX_LOCK只是一个空方法,acquisitionSemaphore对象也是空的;如果isAllowPoolSuspension值调整为true,当收到MBean的suspend调用时将会一次性acquisitionSemaphore.acquireUninterruptibly从此信号量获取给定数目MAX_PERMITS 10000的许可,在提供这些许可前一直将线程阻塞。之后HikariPool的getConnection方法获取不到连接,阻塞在suspendResumeLock.acquire(),除非resume方法释放给定数目MAX_PERMITS 10000的许可,将其返回到信号量
close
连接有借有还,连接检测的task也是会关闭的。 我们看一下com.zaxxer.hikari.pool.ProxyConnection源码,
在connection的close的时候,delegate != ClosedConnection.CLOSED_CONNECTION时会调用leakTask.cancel();取消检测连接泄露的task。
在closeStatements中也会关闭:
在checkException中也会关闭
在com.zaxxer.hikari.pool.HikariPool的evictConnection中,也会关闭任务
小结关闭任务如下图所示:
测试模拟
我们可以根据本文对于leakDetectionThreshold的分析用测试包里的com.zaxxer.hikari.pool.MiscTest代码进行适当参数调整模拟连接泄漏情况,测试代码如下:
当代码执行到了quietlySleep(SECONDS.toMillis(4));时直接按照预期抛异常Apparent connection leak detected。
紧接着在close的过程中执行到了delegate != ClosedConnection.CLOSED_CONNECTION来进行leakTask.cancel()
完整的测试输出模拟过程如下所示:
Spark/Scala连接池泄漏问题排查
金融中心大数据决策数据组的同学找到反馈了一个问题:
我们在同一个jvm 需要连接多个数据库时,发现总体上 从连接池borrow 的 connection 多于 归还的,一段时间后 连接池就会报出