问题描述
系统上线一段时间后,经常会报查:
询数据表总数失败: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参数 | 动态追加参数 | 固定拼接 | 无本质区别 |
| 连接池命名 | 有 | 无 | 便于日志追踪 |