且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

奇奇怪怪的问题-Druid+Oracle连接超时关闭问题

更新时间:2022-06-28 05:24:58

记录一下生产环境遇到的问题

生产上的一个程序跑了一段时间后,老是出现 Closed Connection异常,往上追溯错误,发现有关闭连接失败异常

该应用环境:

Oracle + Druid + Spirngboot 2.2.9.RELEASE

先搭建本地环境还原报错

环境搭建

引入依赖

主要是druid oracle springboot依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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: test
spring:
  datasource:
    driver-class-name: oracle.jdbc.OracleDriver
    username: test
    password: test
    url: jdbc:oracle:thin:@192.168.164.110:1521:helowin
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 10
      max-active: 10
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      breakAfterAcquireFailure: true
      timeBetweenConnectErrorMillis: 30000
      min-evictable-idle-time-millis: 30000 # 超过此时间关闭 除去最小空闲连接之外超过此时间的空闲连接
      max-evictable-idle-time-millis: 50000 # 超过此时间关闭 除去所有的空闲连接
      timeBetweenEvictionRunsMillis: 180000 # 监测空闲连接的时间间隔
      min-idle: 4


模拟程序

使用mybatis开发一个定时1s查询一次数据库的程序即可

package com.example.demo.controller;
import com.example.demo.domain.User;
import com.example.demo.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @Author: Zy
 * @Date: 2021/11/26 14:55
 */
@RestController
@Slf4j
public class TimerTestController {
    @Autowired
    UserMapper userMapper;
    @Scheduled(cron = "*/1 * * * * ?")
    public void test() {
        User user = new User();
        user.setUserName("test");
        List<User> select = userMapper.select(user);
        log.info(select.toString());
    }
}




测试

猜想

测试前先对报错原因进行猜想,首先从生产环境的报错位置:

#com.alibaba.druid.util.JdbcUtils$close(Connection x)方法

public static void close(Connection x) {
        if (x != null) {
            try {
                x.close();
            } catch (Exception var2) {
                LOG.debug("close connection error", var2);
            }
        }
    }


此方法调用时机为关闭druid连接池种的连接时调用,此方法内部再调用Connection.Close方法关闭数据库连接

那么猜想出现此报错的原因是调用Connection.close方法时该数据库连接已关闭,是否是因为已关闭的连接再次调用Close方法导致的问题?

测试

启动应用程序

观察日志,确认每次都查询正常

奇奇怪怪的问题-Druid+Oracle连接超时关闭问题


数据库

查看数据库连接数,发现与服务启动时初始化的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+Oracle连接超时关闭问题




总结

经过分析,可以得出结论,出现该异常的原因是:

连接池中初始化了大量的空闲连接,这些连接在一段时间后,超过了数据库的连接超时时间,此时数据库就会单方面关闭这些连接,但是应用中连接池中的连接并没有关闭,等到有新的请求到达,从连接池中获取连接时,该连接其实已经被数据库关闭,此时就会出现该错误

或者,druid每隔一段时间,就会对连接池中的连接进行有效性检查,如果该连接超过了配置的空闲连接时间,就会调用JdbcUtils.close()方法,但是该连接已经被数据库关闭了,此时就会报错 closed connection

跟此次问题相关的druid连接池配置详解

min-evictable-idle-time-millis: 30000 # 超过此时间关闭 除去最小空闲连接之外超过此时间的空闲连接
max-evictable-idle-time-millis: 50000 # 超过此时间关闭 除去所有的空闲连接
timeBetweenEvictionRunsMillis: 180000 # 监测空闲连接的时间间隔


解决办法

复现了问题,来记录下解决办法

  1. 调整oracle数据库的超时连接配置,配置为无限制
    1. oracle数据库修改用户profiles的idle_time mysql数据库修改数据库配置 wait_time
  1. 修改druid连接池配置
    1. 调低 timeBetweenEvictionRunsMillis 扫描间隔, 这意味着druid会更频繁的扫描连接池中的无效连接
    2. 调低 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 一致