旧数据源连接池未销毁及ThreadLocal残留导致资源泄漏,需同步释放连接池、清理ThreadLocal、启用Dynamic-datasource销毁器;显式注册DefaultDataSourceDestroyer,开启grace-destroy,严格配对push/clear,配置连接池泄漏检测与生命周期控制,并通过Actuator、jstat、jmap验证销毁效果。

核心问题在于旧数据源的连接池未被主动销毁,同时线程上下文残留导致资源长期被持有。解决需从连接池释放、ThreadLocal清理、销毁机制启用三方面同步推进。
确保数据源销毁器正确注册并启用
Dynamic-datasource 默认不自动启用销毁逻辑,必须显式配置销毁器 Bean:
- 在 Spring 配置类中声明 DefaultDataSourceDestroyer 实例,它是唯一支持 Hikari/Druid/DBCP2 连接池检测与关闭的实现
- 开启优雅销毁开关:
spring.datasource.dynamic.grace-destroy=true,否则移除数据源时会跳过活跃连接等待流程 - 避免自定义销毁器未实现
DataSourceActiveDetector,否则无法识别连接是否真正空闲,导致强制关闭或漏关
强制清理 ThreadLocal 中的数据源上下文
DynamicDataSourceContextHolder 使用 Deque<string></string> 栈结构存储切换记录,手动调用 push() 后若未配对 poll() 或未执行 clear(),就会造成线程复用时上下文堆积:
- 所有 Filter、Interceptor、AOP 切面中使用
push()的地方,必须包裹在try-finally块内,并在 finally 中调用DynamicDataSourceContextHolder.clear() - 禁止在异步线程(如
CompletableFuture、线程池任务)中直接复用主线程的上下文;如需传递,须手动拷贝并确保子线程结束前 clear - 检查是否有框架层(如某些事务代理)意外保留了 ThreadLocal 引用,可借助 MAT 分析堆转储中
LOOKUP_KEY_HOLDER的引用链
配合连接池自身泄漏防护机制
仅靠 dynamic-datasource 的销毁器不够,需让底层连接池主动参与泄漏防控:
- HikariCP:设置
leak-detection-threshold=60000(单位毫秒),触发时打印堆栈,精准定位未关闭连接的代码位置 - Druid:启用
removeAbandonedOnMaintenance=true和removeAbandonedTimeoutMillis=60000,定期回收疑似泄漏连接 - 统一配置连接最大生命周期(
max-lifetime)小于数据库wait_timeout,避免连接因服务端断连而滞留在池中
验证销毁是否真正生效
不能只看日志“destroyed”,要确认连接数归零:
- 调用
/actuator/datasources(需引入 Actuator)查看各数据源当前活跃连接数和总连接数 - 移除数据源后,用
jstat -gc <pid></pid>观察老年代增长是否放缓,或用jmap -histo <pid></pid>检查HikariPool$PoolEntry等对象数量是否下降 - 对关键数据源启用手动销毁测试:调用
DynamicRoutingDataSource.removeDataSource("xxx")后,观察连接池 MBean(如 Hikari 的HikariPoolMXBean)中getActiveConnections()是否归零

















