背景:
项目是用的springboot,连接池用的是hikaricp,且数据库连接做了LB配置,之前就是经常会有数据库出现问题,专家给到的解决方案。
数据连接io超时报错,排查了当时数据库各项指标都无显示异常,且也没有获取到当时的queryId,给出的解决方案是增加重试机制 ,但是成本太高,故自己根据日期排查下问题,日志如下
错误信息:
org.postgresql.util.PSQLException: An I/O error occurred while sending to the backend.<MSG>Read timed out
这条日志信息表明在与PostgreSQL数据库通信时发生了I/O错误,具体是读操作超时 (Read timed out
)。
详细调用栈跟踪:
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:383)
这个调用来自PostgreSQL JDBC 驱动程序,具体是在执行查询的时候抛出的异常。
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:496)
位于 PgStatement
类,该类负责执行SQL语句。
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:413)
这个方法负责执行普通的SQL语句。
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:190)
表示这是通过PreparedStatement执行的SQL语句。
at org.postgresql.jdbc.PgPreparedStatement.executeUpdate(PgPreparedStatement.java:152)
具体执行的是更新操作(executeUpdate
表示是INSERT、UPDATE或DELETE等更新操作)。
JDBC4的isValid方法来测试连接是否可用,是通过向数据库服务器发送一个ping请求来实现的。这个ping请求的实现方式可能因数据库厂商而异,但通常包括向数据库服务器发送一个简单的网络数据包,以测试连接是否正常。
JDBC4的isValid方法的原理是基于底层网络连接的有效性进行检测,它使用了底层协议的心跳机制来检测连接的有效性。当调用isValid方法时,JDBC驱动程序会发送一个心跳包到数据库服务器,等待数据库服务器的响应。如果在指定的超时时间内收到了响应,则认为连接是有效的,否则认为连接已经失效。
at org.postgresql.jdbc.PgConnection.isValid(PgConnection.java:1465)
这里可以看到驱动程序在检查连接是否有效。(到这里就可以定位是校验获取的连接有效性出了问题,也就可以理解了当时为什么查不到queryId了)
at com.zaxxer.hikari.pool.PoolBase.isConnectionAlive(PoolBase.java:161)
HikariCP连接池在检查某个连接是否仍然活跃。
at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:186)
at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:162)
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:100)
HikariCP连接池尝试获取一个连接。这个连接可能因为前述错误而失败。
at com.baomidou.dynamic.datasource.ds.ItemDataSource.getConnection(ItemDataSource.java:56)
at com.baomidou.dynamic.datasource.ds.AbstractRoutingDataSource.getConnection(AbstractRoutingDataSource.java:48)
显示使用的是动态数据源管理工具(例如MyBatis-Plus Dynamic Datasource),尝试从HikariCP连接池获取连接。
at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils.java:159)
at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:117)
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:80)
Spring JDBC 工具类尝试获取连接,从而对数据库进行操作。
at org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction.java:80)
at org.mybatis.spring.transaction.SpringManagedTransaction.getConnection(SpringManagedTransaction.java:67)
MyBatis在Spring管理的事务中打开一个连接。同样反映出当前使用了Spring事务管理。
at org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor.java:345)
at com.baomidou.mybatisplus.core.executor.MybatisSimpleExecutor.prepareStatement(MybatisSimpleExecutor.java:93)
MyBatis执行器获取连接并准备执行SQL语句。
at com.baomidou.mybatisplus.core.executor.MybatisSimpleExecutor.doQuery(MybatisSimpleExecutor.java:68)
at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:333)
MyBatis执行查询操作,并从数据库中获取结果。
总结
从上述日志信息中,我们可以看到操作流程如下:
- 应用程序使用 MyBatis 和 Spring 获取数据库连接。
- 使用 HikariCP 作为连接池管理数据库连接。
- 在 HikariCP 中检查连接是否有效时,发生了 I/O 错误(读超时)。
- 这导致 PostgreSQL 驱动程序抛出
PSQLException
异常。
这通常表示网络问题、数据库配置问题或者客户端配置问题。需要进一步检查网络连接、数据库以及连接池的相关配置来排查具体原因。
具体的原因没定位出来,但是目前可以通过配置提升用户体感
hikari:
# 数据库连接有效性校验超时时间(ms) 默认是5秒
validation-timeout: 500
HikariCP 是怎么检查连接是否有效
HikariCP 是一个高性能的 JDBC 连接池,在管理数据库连接的过程中,它提供了一些机制来检查连接是否有效,以保证连接的可用性和稳定性。下面将详细介绍 HikariCP 如何检查连接是否有效:
1. Connection Test Options
HikariCP 提供了几个参数来配置连接的检测和验证,这些配置项帮助确保连接池中的连接是可用的:
connectionTestQuery 测试语句,不推荐配置
validationTimeout 默认5秒 验证是否有效的超时时间
最小设置为1秒毫秒数据会转为秒
- final int validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000;
idleTimeout 默认10分钟
这个属性控制连接池中空闲连接的最大空闲时间,只有当连接池中连接数量大于最小连接数量(
minimumIdle
)时会生效
maxLifetime 默认30分钟
- 这个属性控制连接池中一个连接的最大生存时间,当一个连接的生存时间大于这个值且没有正在被使用时,将会被关掉
与idleTimeout区别
max-lifetime控制连接的总的生命周期,无论当前连接数是否大于最小连接数量,都会关掉生命周期完结的连接,idle-timeout只控制空闲且大于最小连接数量的那部分连接
- 这个属性控制连接池中一个连接的最大生存时间,当一个连接的生存时间大于这个值且没有正在被使用时,将会被关掉
connectionTimeout 默认30秒
-
此属性控制客户端等待来自连接池的连接的最大毫秒数。如果超过这个时间而没有连接可用,将抛出SQLException。最低可接受的连接超时时间是250毫秒。默认值:30000(30秒)
-
-
leakDetectionThreshold 默认0 不开启连接泄露检测
2. 无需显式配置的默认行为
HikariCP 默认情况下使用 JDBC 驱动程序提供的 isValid
方法来验证连接的有效性。isValid
方法通过尝试与数据库进行简单的通信验证连接是否有效。
3. 配置 connectionTestQuery
在某些情况下,具体的 JDBC 驱动程序可能不支持 isValid
方法,或者你想使用自定义的查询语句来验证连接。这时可以使用 connectionTestQuery
参数。
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/yourdb");
config.setUsername("yourusername");
config.setPassword("yourpassword");
config.setConnectionTestQuery("SELECT 1");
HikariDataSource dataSource = new HikariDataSource(config);
4. 工作方式
当 HikariCP 需要验证连接时,它会执行以下步骤:
-
默认使用
isValid
方法:boolean isValid = connection.isValid(validationTimeout);
这个方法会使用数据库驱动提供的
isValid
方法,可以设置validationTimeout
来指定超时。如果连接在规定时间内响应,那么这个连接被认为是有效的。 -
使用自定义
connectionTestQuery
:如果配置了
connectionTestQuery
,HikariCP 会执行该查询来验证连接。这个查询应该是快速并且无副作用的,例如SELECT 1
。如果执行成功,这个连接被认为是有效的。具体代码逻辑如下:try (Statement statement = connection.createStatement()) { statement.executeQuery(connectionTestQuery); // If the query executes successfully, the connection is valid } catch (SQLException e) { // If query execution fails, the connection is considered invalid }
5. 使用 validationTimeout
HikariCP 提供了 validationTimeout
参数来配置连接验证的超时时间:
config.setValidationTimeout(5000); // 设置验证超时时间为5秒
6. 连接池中的连接检查周期
HikariCP 在以下情况会进行连接检查:
- 获取新连接:当应用请求新连接时,先检查当前连接是否有效。
- 闲置连接:使用
idleTimeout
配置检查闲置连接是否应该被移除。 - 最大连接生命周期:使用
maxLifetime
配置确保连接在规定的生命周期内使用,超过时间一律关闭,以避免潜在的资源泄漏或者某些数据库的限制。
config.setIdleTimeout(600000); // 闲置10分钟后移除连接
config.setMaxLifetime(1800000); // 连接最大生命周期为30分钟
config.setConnectionTimeout(30000); // 连接超时设置为30秒