您当前的位置:首页 > 互联网教程

记一次Druid连接池不释放的问题

发布时间:2025-05-25 07:35:20    发布人:远客网络

记一次Druid连接池不释放的问题

一、记一次Druid连接池不释放的问题

1、发现Druid连接池不释放的问题,分析原因后得知是业务代码导致的数据库连接未释放,而非达梦数据库兼容性问题。

2、为了监控Druid连接池状态,在配置类中新增监控Bean。配置文件中加入监控参数,便于实时查看连接使用情况。

3、通过访问监控页面,输入配置的IP与端口号,然后输入账号密码登录,进入数据源页面。当发现连接不释放时,监控页面显示连接总数与关闭次数不一致。

4、深入分析,点击查看活跃连接堆栈,发现其中存在大量相同的StackTrace,表明有大量重复连接占用资源。

5、定位到相关代码,发现调用dataSource.getConnection()后手动创建了数据库连接,但并未进行正确释放。原因是为兼容达梦数据库,使用了getMetaData方法获取连接信息,导致连接未被正确回收。

6、修改代码逻辑,确保在使用完毕后正确释放连接,问题得以解决。证明与达梦数据库的兼容性无关,而是业务代码中连接管理不当导致的问题。

二、记一次测试druid连接池关闭后线程挂起的bug

项目使用了阿里druid jdbc连接池。为了测试高并发查询情况下关闭连接池的性能,我们编写了一个单元测试,该测试启动多个线程不断获取连接查询。其中,一个线程在执行特定查询后生成新的连接池替换旧的,随后关闭旧的连接池,循环多次。测试目的是发现内存泄露问题,但后来发现某些线程卡在了druid数据源的`takeLast`方法处,导致测试任务无法结束。

测试代码可以在最新版本的druid(1.1.21)上重现问题,无需设置`maxWait`,因为生产环境通常会设置`maxWait`避免此问题。问题的根源在于连接池关闭时,`notEmpty.signalAll()`已被调用,但某些线程仍然在阻塞等待释放的连接,卡在`takeLast`方法中。通过查看代码,我们发现连接池关闭方法中调用了`notEmpty.signalAll()`,理应解除阻塞,但实际情况下,线程仍被阻塞。

问题源于druid中`takeLast`方法的实现,该方法在调用`notEmpty.await();`之前未判断连接池是否已经关闭。这可能导致在调用此方法前连接池已关闭,但此处未进行判断,从而引发线程卡住的问题。通过在`notEmpty.await();`前添加判断连接池是否关闭的逻辑,问题得以解决。

实际上,这个问题并不严重。在正常生产环境下,druid连接池通常会设置`maxWait`参数,这使得在获取连接时优先调用`pollLast()`,避免使用`takeLast()`。此外,`maxWait`参数允许的最大阻塞时间限制了线程阻塞的时间,因此不会出现卡住的问题。我们的测试情况较为极端,这可能是问题未在生产环境中出现的原因。

三、druid连接池中的连接数与预期不一致的一次分析

深入剖析druid连接池连接数与预期不一致的原因与解决方案

在排查生产环境cat监控显示部分应用SQL执行耗时长的问题时,我们发现部分应用节点在运行一段时间后,数据库连接池中的实际连接数与应用中配置的参数不一致。主要表现为连接池中的可用连接总数远小于应用配置的初始连接数与最小空闲连接数。这导致了新建连接耗时增加,线程阻塞状态出现,影响性能,不利于应对外部瞬间的并发压力。条码组项目使用的是druid框架,但使用版本较为杂乱,包括1.0.22、1.1.10、1.1.22。基于此,本文旨在分析druid如何对空闲连接进行回收,以及提供相应的配置建议与jar包版本建议。

通过对源码的反编译分析,我们发现druid框架在应用启动后启动了多个处理线程,其中Druid-ConnectionPool-Destroy-*线程专门用于回收空闲连接。不同版本的使用配置及处理逻辑存在差异。以1.0.22与1.1.22版本为例进行对比分析。

在1.0.22版本中,回收线程按照配置的时间间隔timeBetweenEvictionRunsMillis进行轮询。回收空闲连接的判断逻辑包含如下步骤:

获取超过最小空闲连接数的数量checkCount(连接池总数减去最小空闲连接数)。

获取当前连接的空闲时间idleMillis(当前时间减去上一次连接使用的时间)。

判断idleMillis是否小于minEvictableldleTimeMillis(默认30分钟),小于则退出循环,否则继续后续处理。

如果checkCount大于0,将超过最小空闲连接数的连接放入待销毁集合(进行回收)。

如果idleMillis大于maxEvictableIdleTimeMillis,即使连接池连接数小于最小空闲连接数,当前连接也会被放入待销毁集合。

在1.1.22版本中,新增了判断逻辑:

idleMillis需要同时满足小于minEvictableldleTimeMillis(默认30分钟)与keepAliveBetweenTimeMillis(默认2分钟)时,才会退出循环。

配置keepalive为true时,idleMillis大于keepAliveBetweenTimeMillis,当前连接会被维护到保活集合中,以确保连接可用,避免意外回收。

结合项目现有的配置和依赖的jar包版本,连接池中的连接数不总是与初始连接数或最小空闲连接数保持一致。在使用1.0.22、1.1.10版本的应用中,如果SQL请求不是非常稀少,则能维持连接池中的连接数与配置一致。然而,使用1.1.22版本、处理SQL请求不是非常频繁的应用,空闲连接更容易被回收。关键原因在于缺少关键配置(keepalive未配置),导致默认参数下更容易回收连接。

建议统一升级使用1.1.22版本,并主动排除项目中依赖的其他版本,以防版本冲突。对于使用1.1.22或更高版本的应用,确保添加keepalive=true配置。

Spring Boot项目配置示例:spring.datasource.druid.keep-alive=true