一、closed connection
最近有个需求,单线程监控kafka数据的变化,再由简单的逻辑查询数据库,封装查询接口,使用open-feign调用接口。具体链路如下图所示:
因kafka的数据变化较快,需要频繁查询数据库,所以使用了druid的数据源。程序在测试环境执行没有任何问题,一旦在生产环境上执行就会出现closed connection错误,经过排查分析原因,发现oralce数据库超过一定的时间会把链接断开,当数据源再去使用连接时就会报closed connect错误。
详细分析配置文件中,发现其中几个配置有问题,详细分析以下几个配置
maxActive
最大连接池数量,允许的最大同时使用中的连接数。这里特地唠叨一下,配置 maxActive 千万不要好大喜多,虽然配置大了看起来业务流量飙升后还能处理更多的请求,但切换到 DB 视角会发现其实连接数的增多在很多场景下反而会减低吞吐量,一个非常典型的例子就秒杀,在更新热点数据时 DB 需要加锁操作,这个时候再让更多的连接操作 DB 就有点像假日往高速上涌入的车辆,只会给 DB 添堵。
keepAlive
参数表示是否对空闲连接保活,布尔类型。可能不少人认为 druid 连接池默认会维持DB连接的心跳,对池子中的连接进行保活,特别配置了 minIdle 这个参数后觉得,有了 minIdle 最少应该会保持这么多空闲连接。其实,keepAlive 这个参数是在 druid 1.0.28 后新增的,并且默认值是 false,即不进行连接保活。
那么需要保活连接,是不是将 keepAlive 配置成 true 就完事了呢?虽然 true 的确是开启了保活机制,但是应该保活多少个,心跳检查的规则是什么,这些都需要正确配置,否则还是可能事与愿违。这里需要了解几个相关的参数:minIdle 最小连接池数量,连接保活的数量,空闲连接超时踢除过程会保留的连接数(前提是当前连接数大于等于 minIdle),其实 keepAlive 也仅维护已存在的连接,而不会去新建连接,即使连接数小于 minIdle;minEvictableIdleTimeMillis 单位毫秒,连接保持空闲而不被驱逐的最小时间,保活心跳只对存活时间超过这个值的连接进行;maxEvictableIdleTimeMillis 单位毫秒,连接保持空闲的最长时间,如果连接执行过任何操作后计时器就会被重置(包括心跳保活动作);timeBetweenEvictionRunsMillis 单位毫秒,Destroy 线程检测连接的间隔时间,会在检测过程中触发心跳。保活检查的详细流程可参见源码com.alibaba.druid.pool.DruidDataSource.DestroyTask,其中心跳检查会根据配置使用 ping 或 validationQuery 配置的检查语句。
maxEvictableIdleTimeMillis 和 minEvictableIdleTimeMillis
/**
DestroyTask 线程销毁任务每隔 timeBetweenEvictionRunsMillis
(默认一分钟)的时间会执行一次连接池瘦身检测
**/
if (idleMillis >= minEvictableIdleTimeMillis) {
if (checkTime && i < checkCount) { // checkCount = poolingCount - minIdle
evictConnections[evictCount++] = connection;
continue;
} else if (idleMillis > maxEvictableIdleTimeMillis) {
evictConnections[evictCount++] = connection;
continue;
}
}
checkTime = true 所以会再判断 i < checkCount 是否成立,而 checkCount = poolingCount - minIdle 也就是超过 minIdle 那部分连接(类似线程池的 maximumPoolSize - corePoolSize )
所以,代码逻辑是 1~minIdle 的连接空闲超过 maxEvictableIdleTimeMillis(默认7小时) 则需要清除掉, minEvictableIdleTimeMillis(默认30分钟) 针对的是超过 minIdle 的那部分连接
maxEvictableIdleTimeMillis 表示的是 minIdle 内连接能空闲的最大时长。
因为程序启动时间在中午11:30分左右,这就是为什么程序是在凌晨1:30分左右报错的原因,因配置文件没有配置maxEvictableIdleTimeMillis此参数,默认7个小时,druid要关闭超过最大空闲时间的连接,关闭连接连接时(oracle客户端已经把连接关闭),所以日志报错closed connection。
二、解决办法
1、因为程序是单线程,所以调整了minActive 和minIdle ,把参数值都调整为1,尽量不保留空闲连接,没有空闲连接,就不会关闭连接,就不会报错。
2、开启keep-alive参数设置为true,查看数据库配置的关闭空闲连接的时间,修改maxEvictableIdleTimeMillis的默认值(单位为ms),小于数据库配置的默认时间。select resource_name,resource_type,limit from dba_profiles where profile='DEFAULT' ;