记录一下生产环境遇到的问题
生产上的一个程序跑了一段时间后,老是出现 Closed Connection异常,往上追溯错误,发现有关闭连接失败异常
该应用环境:
Oracle + Druid + Spirngboot 2.2.9.RELEASE
先搭建本地环境还原报错
环境搭建
引入依赖
主要是druid oracle springboot依赖
<projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>demo1</artifactId><version>0.0.1-SNAPSHOT</version><name>demo1</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><spring.boot-version>2.2.9.RELEASE</spring.boot-version></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring.boot-version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.8</version></dependency><dependency><groupId>com.oracle</groupId><artifactId>ojdbc6</artifactId><version>11.2.0.3</version></dependency><dependency><groupId>tk.mybatis</groupId><artifactId>mapper-spring-boot-starter</artifactId><version>2.1.5</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
配置文件
#spring: #profiles: #active: testspring: datasource: driver-class-name: oracle.jdbc.OracleDriverusername: testpassword: testurl: jdbc:oracle:thin: .168.164.110:1521:helowintype: com.alibaba.druid.pool.DruidDataSourcedruid: initial-size: 10max-active: 10validationQuery: SELECT1FROMDUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: falsebreakAfterAcquireFailure: truetimeBetweenConnectErrorMillis: 30000min-evictable-idle-time-millis: 30000#超过此时间关闭除去最小空闲连接之外超过此时间的空闲连接max-evictable-idle-time-millis: 50000#超过此时间关闭除去所有的空闲连接timeBetweenEvictionRunsMillis: 180000#监测空闲连接的时间间隔min-idle: 4
模拟程序
使用mybatis开发一个定时1s查询一次数据库的程序即可
packagecom.example.demo.controller; importcom.example.demo.domain.User; importcom.example.demo.mapper.UserMapper; importlombok.extern.slf4j.Slf4j; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.scheduling.annotation.Scheduled; importorg.springframework.web.bind.annotation.RestController; importjava.util.List; /*** @Author: Zy* @Date: 2021/11/26 14:55*/publicclassTimerTestController { UserMapperuserMapper; cron="*/1 * * * * ?") (publicvoidtest() { Useruser=newUser(); user.setUserName("test"); List<User>select=userMapper.select(user); log.info(select.toString()); } }
测试
猜想
测试前先对报错原因进行猜想,首先从生产环境的报错位置:
#com.alibaba.druid.util.JdbcUtils$close(Connection x)方法
publicstaticvoidclose(Connectionx) { if (x!=null) { try { x.close(); } catch (Exceptionvar2) { LOG.debug("close connection error", var2); } } }
此方法调用时机为关闭druid连接池种的连接时调用,此方法内部再调用Connection.Close方法关闭数据库连接
那么猜想出现此报错的原因是调用Connection.close方法时该数据库连接已关闭,是否是因为已关闭的连接再次调用Close方法导致的问题?
测试
启动应用程序
观察日志,确认每次都查询正常
数据库
查看数据库连接数,发现与服务启动时初始化的druid连接数一致.
select sid, username, paddr, status,program,serial# from v$session t where username ='TEST'and status ='INACTIVE'
杀死数据库连接
为了验证猜想,我们模拟数据库关闭连接,即直接杀掉jdbc连接
-- 生成杀session报文select replace(wm_concat('alter system kill session '''||sid||','||serial#||''';'),',','')from v$session where username ='TEST'and status ='INACTIVE'and program ='JDBC Thin Client'-- 执行杀死开启的数据库连接
观察应用日志
总结
经过分析,可以得出结论,出现该异常的原因是:
连接池中初始化了大量的空闲连接,这些连接在一段时间后,超过了数据库的连接超时时间,此时数据库就会单方面关闭这些连接,但是应用中连接池中的连接并没有关闭,等到有新的请求到达,从连接池中获取连接时,该连接其实已经被数据库关闭,此时就会出现该错误
或者,druid每隔一段时间,就会对连接池中的连接进行有效性检查,如果该连接超过了配置的空闲连接时间,就会调用JdbcUtils.close()方法,但是该连接已经被数据库关闭了,此时就会报错 closed connection
跟此次问题相关的druid连接池配置详解
min-evictable-idle-time-millis: 30000#超过此时间关闭除去最小空闲连接之外超过此时间的空闲连接max-evictable-idle-time-millis: 50000#超过此时间关闭除去所有的空闲连接timeBetweenEvictionRunsMillis: 180000#监测空闲连接的时间间隔
解决办法
复现了问题,来记录下解决办法
- 调整oracle数据库的超时连接配置,配置为无限制
- oracle数据库修改用户profiles的idle_time mysql数据库修改数据库配置 wait_time
- 修改druid连接池配置
- 调低 timeBetweenEvictionRunsMillis 扫描间隔, 这意味着druid会更频繁的扫描连接池中的无效连接
- 调低 min-evictable-idle-time-millis/max-evictable-idle-time-millis 这意味着连接池中的空闲连接在更短的时间内就会被关闭
建议采取第二种方式,原则如下:
timeBetweenEvictionRunsMillis + min-evictable-idle-time-millis < 数据库连接超时时间
max-evictable-idle-time-millis 可以配置的跟 min-evictable-idle-time-millis 一致