HikariPool-1 - Connection is not available, request timed out after 6000ms

问题

==================================================================

今天项目上频繁出现了大量的这样的报错

Caused by: java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 6000ms.

at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:695)

at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:197)

at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:162)

at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128)

at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils.java:158)

at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:116)

at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.ja

必看视频!获取2024年最新Java开发全套学习资料 备注Java

va:79)

… 75 more

百度了一下,很多都说需要把spring.datasource.hikari.connection-timeout的值调大就行。但是我们试了一下之后发现效果并不明显,然后我们调大了spring.datasource.hikari.maximum-pool-size的大小,这下有效果了。这是为什么呢?

排查

==================================================================

一直以来,我都以为这个报错是hikari连接数据库时,创建数据库连接超时抛出的异常,但是翻了一下抛出异常的源码之后发现并不是这样的,简单的贴一下代码:

public Connection getConnection(final long hardTimeout) throws SQLException

{

suspendResumeLock.acquire();

final long startTime = currentTime();

try {

long timeout = hardTimeout;

do {

PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);

if (poolEntry == null) {

break; // We timed out… break and throw exception

}

final long now = currentTime();

if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs && !isConnectionAlive(poolEntry.connection))) {

closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);

timeout = hardTimeout - elapsedMillis(startTime);

}

else {

metricsTracker.recordBorrowStats(poolEntry, startTime);

return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);

}

} while (timeout > 0L);

metricsTracker.recordBorrowTimeoutStats(startTime);

throw createTimeoutException(startTime);

}

catch (InterruptedException e) {

Thread.currentThread().interrupt();

throw new SQLException(poolName + " - Interrupted during connection acquisition", e);

}

finally {

suspendResumeLock.release();

}

}

里面的代码虽然没有细究,但是从意思上看,应该是如果在hardTimeout时间内没有从连接池中获取到有效的连接,那就抛出异常,而抛出的正是日志中大量打印的那个异常。

论证

==================================================================

从上面的源码中看,增加spring.datasource.hikari.connection-timeout的值应该确实是有效的,最起码可以降低异常率,但是总连接数依然不足,所以看起来的现象应该是日志中的报错少了,但是系统会卡的要命。增加spring.datasource.hikari.maximum-pool-size之后整个系统可用的最大连接数变多了,自然就不卡不报错了。

通过demo重现问题

==========================================================================

为了验证我的想法,我写了一个demo

数据库相关配置文件:

#5个最小空闲连接,我把它理解为coreSize

spring.datasource.hikari.minimum-idle=5

#最大生命周期60s,测试用

spring.datasource.hikari.max-lifetime=60000

#一个连接空闲10s后,将会被回收

spring.datasource.hikari.idle-timeout=10

#连接池中的最大连接数

spring.datasource.hikari.maximum-pool-size=10

#获取连接的等待时长5s

spring.datasource.hikari.connection-timeout=5000

#判断连接是否有效

spring.datasource.hikari.connection-test-query=select 1

demo源码:

@SpringBootApplication(scanBasePackages = “com.example.juc.hikari”)

public class Main {

private static final Logger logger = LoggerFactory.getLogger(Main.class);

public static void main(String[] args) {

ConfigurableApplicationContext applicationContext = SpringApplication.run(Main.class, args);

DataSource dataSource = applicationContext.getBean(DataSource.class);

logger.info(“{}”, dataSource);

//先获取10个连接,就获取,不释放

int maxConnectionCount = 10;

List connections = new ArrayList<>();

for (int i = 0; i < maxConnectionCount; i++) {

try {

Connection connection = dataSource.getConnection();

connections.add(connection);

} catch (SQLException e) {

e.printStackTrace();

}

}

//起一个新的

Thread thread = new Thread(() -> {

Connection connection = null;

do {

try {

//开始尝试获取连接

connection = dataSource.getConnection();

} catch (SQLException e) {

logger.error(“连接获取失败:[{}]”, e.getMessage());

}

//如果获取失败,就再来一次

} while (connection == null);

logger.info(“获取到新的连接了!”);

try {

connection.close();

} catch (SQLException e) {

e.printStackTrace();

}

logger.info(“链接归还了!”);

}, “test”);

thread.start();

try {

logger.info(“主线程休眠10s”);

TimeUnit.SECONDS.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

//主线程休眠10秒之后,开始归还连接

connections.forEach(connection -> {

try {

connection.close();

logger.info(“归还了”);

} catch (SQLException e) {

e.printStackTrace();

}

});

最后

码字不易,觉得有帮助的可以帮忙点个赞,让更多有需要的人看到

又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考

以下是部分内容截图
架构面试专题及架构学习笔记导图.png
connection.close();

logger.info(“归还了”);

} catch (SQLException e) {

e.printStackTrace();

}

});

最后

码字不易,觉得有帮助的可以帮忙点个赞,让更多有需要的人看到

又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考

以下是部分内容截图
[外链图片转存中…(img-Abg2PwQU-1716442469564)]

  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值