升级数据库连接池 Druid 避免获取不到连接的问题

升级数据库连接池 Druid 避免获取不到连接的问题

问题描述

我们的产品平台用的数据库连接池是阿里的 Druid:
作为开源组件, 存在 BUG 是在所难免,
陆续收到 2 个项目反馈的问题, 经过平台评估建议项目组升级到最新版本;

项目 1: 问题的现象是在处理 Scada 数据采集时业务出现的问题,

2021-02-02 08:58:50 ERROR JDBCExceptionReporter:234 - wait millis 60000, active 2, maxActive 500

翻译:系统等待 60 秒,没有获得数据库连接,当前系统使用了 2 个连接,配置最大 500;

项目 2: Oracle RAC 集群 其中数据库单节点机器重启过; 导致

wait millis 60012, active 5, maxActive 120

 由于 数据库(集群模式)单节点机器重启,然后数据库开始转到另一个数据库节点, MES 开始无法获取新的连接.

升级方法

升级比较简单, 只需修改配置文件:

项目使用的版本: 1.0.19
建议使用版本: 1.2.6

配置 keepAlive=true,并使用 1.1.16 之后的版本
1:把 druid 升级到 1.2.6 版本(目前的最新版本)

<!-- http://mvnrepository.com/artifact/com.alibaba/druid -->
		<dependency>
		    <groupId>com.alibaba</groupId>
		    <artifactId>druid</artifactId>
		    <version>1.2.6</version>
		</dependency>

2:spring-core 中配置 datasource 的位置增加:
<property name=“keepAlive” value=“true” />

可能不少人认为 druid 连接池默认会维持 DB 连接的心跳,对池子中的连接进行保活,特别配置了 minIdle 这个参数后觉得,有了 minIdle 最少应该会保持这么多空闲连接。其实,keepAlive 这个参数是在 druid 1.0.28 后新增的,并且默认值是 false,即不进行连接保活。

推荐配置:如果网络状况不佳,程序启动慢或者经常出现突发流量,则推荐配置为 true;

 <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->  
		    <property name="timeBetweenEvictionRunsMillis" value="60000" />  
		   
		    <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->  
		    <property name="minEvictableIdleTimeMillis" value="300000" />  
		    
		    <property name="validationQuery" value="${jdbc.validation}" />  
		    <property name="keepAlive" value="true" />
		    <property name="testWhileIdle" value="true" />  
		    <property name="testOnBorrow" value="false" />  
		    <property name="testOnReturn" value="false" />  

从连接池层面做连接的检测保活操作,检测连接是否可用
升级 jar 开发环境先验证没有问题就可以使用;

关于 Druid

Druid 是一个 JDBC 组件库,包含数据库连接池、SQL Parser 等组件, 被大量业务和技术产品使用或集成,经历过最严苛线上业务场景考验,是你值得信赖的技术产品。

  1. Druid 是什么?
    Druid 是 Java 语言中最好的数据库连接池。Druid 能够提供强大的监控和扩展功能。
    https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98

Druid 发布版本及相关修复问题

Druid 发布 1.2.6 版本,增强 SQL Parser,修复连接池在特定场景异常的问题
1. 修复连接池在 close 后创建中的连接没有被关闭的问题 #4196 #4195
2. 修复连接池在 timeBetweenEvictionRunsMillis 大于 keepAliveBetweenTimeMillis 时异步使用连接会导致连接池不可用的问题。

Druid 发布 1.1.24 版本,增强对 PG 的支持
1. 修复连接池在 KeepAlive 打开并且使用 CreateScheduler 偶发连接无法创建的问题

Druid 发布 1.1.22 版本
1. 修复在高强度并发切换数据库主从库时偶发状态不对的问题

Druid 发布 1.1.16 版本 修复 keepAlive 打开时连接池空闲时不会缩容的问题

Druid 发布 1.1.14 版本 修复 keepAlive 打开时偶发连接泄露的问题

Druid-1.1.6 发布 增强连接池和 SQL Parser
1. 修复连接池 DruidPooledConnection.isClosed 判断直接使用物理连接逻辑的问题

Druid-1.1.5 版本发布,修复连接池 testWhileIdle 某些场景不起作用的问题
这个版本涉及连接池两个重要 BUG 修复,包括 testWhileIdle 某些场景不起作用和网络中断时重连时间过长(15 分钟)的问题,建议升级。

分析 1

Druid: 1.0.5

1.0.5 有设计缺陷, 以 Mysql 连接获取为例:

DruidConnectionHolder holder = null;
try {
    holder = new DruidConnectionHolder(DruidDataSource.this, connection);
} catch (SQLException ex) {
    //这里也会退出
    LOG.error("create connection holder error", ex);
    break;
}

不合理,单个的 SQLException 也会导致整个的生存线程结束。
问题的原因就是 druid 在获取 mysql 数据库连接后创建 DruidConnectionHolder 时由于网络原因报了 MySQLException 导致了 CreateConnectionThread 退出。

2017-07-14 00:26:59,609 [Druid-ConnectionPool-Create-190586441] ERROR com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread (DruidDataSource.java:1682) - create connection holder error
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet successfully received from the server was 0 milliseconds ago.  The last packet sent successfully to the server was 0 milliseconds ago.
        at sun.reflect.GeneratedConstructorAccessor33.newInstance(Unknown Source)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
        at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
        at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1121)
        at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3673)
        at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3562)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4113)
        at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2570)
        at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2731)
        at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2812)
        at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2761)
        at com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1612)
        at com.mysql.jdbc.ConnectionImpl.getTransactionIsolation(ConnectionImpl.java:3352)
        at com.alibaba.druid.filter.FilterChainImpl.connection_getTransactionIsolation(FilterChainImpl.java:347)
        at com.alibaba.druid.filter.FilterAdapter.connection_getTransactionIsolation(FilterAdapter.java:872)
        at com.alibaba.druid.filter.FilterChainImpl.connection_getTransactionIsolation(FilterChainImpl.java:344)
        at com.alibaba.druid.filter.FilterAdapter.connection_getTransactionIsolation(FilterAdapter.java:872)
        at com.alibaba.druid.filter.FilterChainImpl.connection_getTransactionIsolation(FilterChainImpl.java:344)
        at com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.getTransactionIsolation(ConnectionProxyImpl.java:260)
        at com.alibaba.druid.pool.DruidConnectionHolder.<init>(DruidConnectionHolder.java:92)
        at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:1680)
Caused by: java.net.SocketException: Connection reset
        at java.net.SocketInputStream.read(SocketInputStream.java:189)
        at java.net.SocketInputStream.read(SocketInputStream.java:121)
        at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:114)
        at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:161)
        at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:189)
        at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3116)
        at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3573)
        ... 16 more

查看了版本 1.1.2 这部分的代码,发现这部分代码已经重构了,不存在该问题,所以升级版本即可。

## 分析 2
Druid: 1.1.10

https://github.com/alibaba/druid/issues/3889
https://github.com/alibaba/druid/issues/3148
数据库宕库后重启,连接池无法创建新的连接
版本:1.1.14
网络断开后,在大概半小时左右,重新连网,获取数据库连接开始报错

com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 14111, active 0, maxActive 10, creating 1, createElapseMillis 44234, createErrorCount 5  
at com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource.java:1720)  
at com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:1402)  
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1382)  
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1372)  
at DBConnectionPool.getConnection(DBConnectionPool.java:73)  
at TaskDBOperation.getPlanInfo(TaskDBOperation.java:240)  
at PlanManager.getAllPlan(PlanManager.java:25)  
at Server$1.run(Server.java:88)  
at java.util.TimerThread.mainLoop(Timer.java:555)  
at java.util.TimerThread.run(Timer.java:505)  
Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

解决方案

升级到最新的稳定版本.

其他常见问题

16. Druid 中的 maxIdle 为什么是没用的?

maxIdle 是 Druid 为了方便 DBCP 用户迁移而增加的,maxIdle 是一个混乱的概念。连接池只应该有 maxPoolSize 和 minPoolSize,druid 只保留了 maxActive 和 minIdle,分别相当于 maxPoolSize 和 minPoolSize。

33. 如何设置为让连接池知道数据库已经断开了,并且自动测试连接查询

加入以下配置:

<!-- 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 -->
<property name="validationQuery" value="SELECT 1" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />