数据库连接池Connection is not available, request timed out after 120000ms

问题描述

系统上线一段时间后,经常会报查:

询数据表总数失败:HikariPool-2 - Connection is not available, request timed out after 120000ms.

系统背景:系统中有定时任务,可能定期需要统计数据库下面表的数据信息

代码

方案1

复制代码
@Component
public class DorisPoolConfig {



    private static final Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();

    public static final String DATABASE_PARAM = "?allowPublicKeyRetrieval=true&serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false";
    public static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";


    private static final int maxPoolSize = 10;
    private static final int minIdle = 4;
    //2分钟
    private static final long connectionTimeout = 2 * 60 * 1000;

    // 5分钟
    private static final long idleTimeout = 5 * 60 * 1000;
    // 30分钟
    private static final long maxLifetime = 30 * 60 * 1000;


    /**
     * 获取默认引擎连接,不同引擎拥有不同的引擎配置
     *
     * @param engine Doris引擎配置
     * @return Doris连接信息
     */
    public synchronized DataSource getEngineDataSource (Engine engine) {
        return getDataSourceByPrefix(engine, LjPoolEnum.PAGE.getCode());
    }


    /**
     * 获取默认引擎连接,不同引擎拥有不同的引擎配置
     *
     * @param engine Doris引擎配置
     * @return Doris连接信息
     */
    public synchronized DataSource getDataSourceByPrefix (Engine engine,String prefix) {
        String key = prefix + engine.getId();
        //获取数据库类型对应的DataSource
        DataSource dataSource = dataSourceMap.get(key);
        if (Objects.isNull(dataSource)) {
            //获取doris连接信息
            dataSource = getInitDataSource(engine);
            dataSourceMap.put(key, dataSource);
        }
        return dataSource;
    }

    /**
     * 获取Doris引擎连接
     *
     * @param engine Doris引擎配置
     * @return Doris连接信息
     */
    private DataSource getInitDataSource(Engine engine) {
        //配置连接池
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(engine.getUrl() + DATABASE_PARAM);
        config.setDriverClassName(JDBC_DRIVER);
        config.setUsername(engine.getUsername());
        config.setPassword(engine.getPassword());
        config.setMaximumPoolSize(maxPoolSize);
        config.setMinimumIdle(minIdle);
        config.setConnectionTimeout(connectionTimeout);
        config.setIdleTimeout(idleTimeout);
        config.setMaxLifetime(maxLifetime);
        return new HikariDataSource(config);
    }
}
复制代码
调用代码
复制代码
    public long executeQueryCount(DataSource dataSource, String countSql, String exceptionMsg) {
        // 验证SQL是否安全,防止SQL注入
        SqlParserUtil.validateSqlBasic(countSql);
        try (Connection conn = dataSource.getConnection();
             PreparedStatement stmt = conn.prepareStatement(countSql);
             ResultSet rs = stmt.executeQuery()) {
            if (rs.next()) {
                return rs.getLong(CommonConstant.ONE);
            }
            return CommonConstant.ZERO;
        } catch (Exception e) {
            exceptionMsg = StringUtils.defaultIfBlank(exceptionMsg, "执行executeQueryCount方法失败");
            log.error(exceptionMsg + ";sql【{}】", countSql);
            throw new CestcBusinessException(HttpCode.BAD_REQUIRED_ERROR,String.format("%s:%s", exceptionMsg,
                    e.getMessage()));
        }
    }
复制代码
分析代码,发现连接池的参数设置有问题,修改后代码

方案2

复制代码
package cn.cestc.servicequality.config;

import cn.cestc.bigdata.common.bean.Engine;
import cn.cestc.servicequality.enums.LjPoolEnum;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 数据库连接池
 *
 */
@Component
public class DorisPoolConfig {



    private static final Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();

    public static final String DATABASE_PARAM = "?allowPublicKeyRetrieval=true&serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false";
    public static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
    static final int CONNECT_TIMEOUT_MS = 10 * 1000;
    static final int SOCKET_TIMEOUT_MS = 10 * 60 * 1000;
    static final long LEAK_DETECTION_THRESHOLD = 60 * 1000;


//连接池数量增加,也可以改成nacos动态配置
    private static final int maxPoolSize = 30;
    private static final int minIdle = 4;
    //2分钟
    private static final long connectionTimeout = 2 * 60 * 1000;

    // 5分钟
    private static final long idleTimeout = 5 * 60 * 1000;
    // 30分钟
    private static final long maxLifetime = 30 * 60 * 1000;


    /**
     * 获取默认引擎连接,不同引擎拥有不同的引擎配置
     *
     * @param engine Doris引擎配置
     * @return Doris连接信息
     */
    public synchronized DataSource getEngineDataSource (Engine engine) {
        return getDataSourceByPrefix(engine, LjPoolEnum.PAGE.getCode());
    }


    /**
     * 获取默认引擎连接,不同引擎拥有不同的引擎配置
     *
     * @param engine Doris引擎配置
     * @return Doris连接信息
     */
    public synchronized DataSource getDataSourceByPrefix (Engine engine,String prefix) {
        String key = prefix + engine.getId();
        //获取数据库类型对应的DataSource
        DataSource dataSource = dataSourceMap.get(key);
        if (Objects.isNull(dataSource)) {
            //获取doris连接信息
            dataSource = getInitDataSource(engine, prefix);
            dataSourceMap.put(key, dataSource);
        }
        return dataSource;
    }

    /**
     * 获取Doris引擎连接
     *
     * @param engine Doris引擎配置
     * @return Doris连接信息
     */
    private DataSource getInitDataSource(Engine engine, String prefix) {
        //配置连接池
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(buildJdbcUrl(engine.getUrl()));
        config.setDriverClassName(JDBC_DRIVER);
        config.setUsername(engine.getUsername());
        config.setPassword(engine.getPassword());
        applyPoolSettings(config, engine, prefix);
        return new HikariDataSource(config);
    }

    static String buildJdbcUrl(String jdbcUrl) {
//设置超时时间
        String timeoutParam = "connectTimeout=" + CONNECT_TIMEOUT_MS + "&socketTimeout=" + SOCKET_TIMEOUT_MS;
        if (jdbcUrl.contains("?")) {
            return jdbcUrl + "&" + DATABASE_PARAM.substring(1) + "&" + timeoutParam;
        }
        return jdbcUrl + DATABASE_PARAM + "&" + timeoutParam;
    }

    static void applyPoolSettings(HikariConfig config, Engine engine, String prefix) {
//新增poolName,便于排查问题
        config.setPoolName(prefix + engine.getId());
        config.setMaximumPoolSize(maxPoolSize);
        config.setMinimumIdle(minIdle);
        config.setConnectionTimeout(connectionTimeout);
        config.setIdleTimeout(idleTimeout);
        config.setMaxLifetime(maxLifetime);
        config.setLeakDetectionThreshold(LEAK_DETECTION_THRESHOLD);
       config.setRegisterMbeans(true);
 // 关键:检测死连接
    config.setConnectionTestQuery("SELECT 1");
    config.setValidationTimeout(50000);
    
    // 可选:定期保活(每30秒)
    config.setKeepaliveTime(30000);
    }
}

// 关键:检测死连接

config.setConnectionTestQuery("SELECT 1");

config.setValidationTimeout(5000);

// 可选:定期保活(每30秒)

config.setKeepaliveTime(30000);

方案对比

方面 实现1 实现2 对"找不到连接"的影响
连接超时 10s connect + 600s socket 无显式设置(依赖驱动默认值) 可能改善网络不稳定场景
泄漏检测 60s 有助于发现连接未关闭的bug
JMX监控 开启 关闭 便于运维排查
最大连接数 20 10 并发压力大时可能减少等待
URL参数 动态追加参数 固定拼接 无本质区别
连接池命名 便于日志追踪
相关推荐
2301_803934618 小时前
SQL如何进行分组后字符串拼接_使用GROUP_CONCAT或STRING_AGG
jvm·数据库·python
爱喝水的鱼丶8 小时前
SAP-ABAP:数据类型与数据对象(8篇) 第四篇:关系映射篇——从类型定义到对象实例的转化逻辑
开发语言·数据库·学习·sap·abap
csdn小瓯8 小时前
Pydantic V2 模型校验与配置管理最佳实践
运维·数据库·windows
网管NO.18 小时前
MySQL、Oracle、PostgreSQL 深度对比,数据库怎么选?
数据库·mysql·oracle
新新学长搞科研8 小时前
【安徽大学主办】第五届半导体与电子技术国际研讨会(ISSET 2026)
大数据·数据库·人工智能·自动化·信号处理·半导体·电子
SelectDB8 小时前
PB 级自动驾驶数据秒级检索:Apache Doris 统一多模态数据平台实践
大数据·数据库·数据分析
Gauss松鼠会8 小时前
【GaussDB】基于SpringBoot实现操作GaussDB(DWS)的项目实战
java·数据库·经验分享·spring boot·后端·sql·gaussdb
難釋懷8 小时前
Redis内存回收-内存淘汰策略
前端·数据库·redis
一只fish8 小时前
Oracle官方文档翻译《Database Concepts 26ai》第7章-数据完整性
数据库·oracle