问题现象:
服务频繁出现 java.lang.IllegalStateException: Pool not open 报错。
问题原因:
连接池的实现中一般会通过定时任务对失效连接进行过期检测,并及时剔除。在dbcp2连接池中,该定时器任务实现类为:BaseGenericObjectPool.Evictor。
一般一个服务会因为连接不同的集群,不同的节点,从而需要创建多个连接池。每个连接池都会有一个对应的 Evictor进行过期连接检测、剔除。
commons-dbcp2:2.1.1包会依赖commons-pool2:2.4.3。该包中这部分功能的实现代码有bug,具体bug如下:
连接池创建时,定时任务启动代码为:
可以看到,这里创建了一个ScheduledThreadPoolExecutor线程池,并调用线程池的scheduleWithFixedDelay方法,开始调度该任务。
而该调度任务的取消逻辑如下:
可以看到,这里主要是通过调用TimerTask.cancel()方法进行调度取消,需要注意的是,该方法是不能取消调度的。只有由Timer定时任务机制进行管理的TimerTask,才能通过调用cancel方法进行取消。上面是由ScheduledThreadPoolExecutor线程池实现的定时任务管理,需要调用提交任务返回的Feature.cancel方法进行取消。
再apache的问题修复记录中也记录了该问题:
一般场景下,当db节点发生变更等情况,导致连接池关闭时,由于该bug。会导致该定时任务不能释放,由此会引发频繁打印日志、内存泄露问题。
解决方案:
升级commons-dbcp2版本即可。升级到2.5.0。