问题
最近将一个项目的 SpringBoot 1.xx 升级到了 SpringBoot 2.xx,顺便把 JDK 也升级到了 15,测试了下没什么问题就发到了生产,但今天项目报错了,HikariPool-1 – Connection is not available, request timed out after 30000ms,而且很快系统就被拖垮了,具体的一些错误如下:
org.springframework.dao.DataAccessResourceFailureException: Unable to acquire JDBC Connection; nested exception is org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:277)
~[spring-orm-5.2.9.RELEASE.jar!/:5.2.9.RELEASE] at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255)
~[spring-orm-5.2.9.RELEASE.jar!/:5.2.9.RELEASE] at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528)
~[spring-orm-5.2.9.RELEASE.jar!/:5.2.9.RELEASE] ...
Caused by: org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:48) ~[hibernate-core-5.4.21.Final.jar!/:5.4.21.Final] at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
~[hibernate-core-5.4.21.Final.jar!/:5.4.21.Final] at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
~[hibernate-core-5.4.21.Final.jar!/:5.4.21.Final] ... Caused by: java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms. at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:695)
~[HikariCP-3.4.5.jar!/:?] at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:197)
~[HikariCP-3.4.5.jar!/:?] at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:162)
~[HikariCP-3.4.5.jar!/:?] at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128)
~[HikariCP-3.4.5.jar!/:?] at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122)
~[hibernate-core-5.4.21.Final.jar!/:5.4.21.Final] ...
解决方法
在网上搜索了一番,很多人都说增加数据库连接池可以解决。确实如此,根据生产的并发量大概计算一下需要多少个连接,修改配置就可以了,比如我的配置:
spring:
datasource:
hikari:
maximum-pool-size: 20
这样也只是解决了部分问题,当压测的时候,并发调到 21 的时候,还是容易出现上述问题,后面根据 这篇文章 的思路找到了问题的所在。
通过错误日志看到错误总是在一个方法的两个地方出错,但这个方法很普通,简单的数据库查询,返回值,类似于:
public Object fun() {
Object data1 = dao.query1();
//...处理数据
Object data2 = dao.query2();
//...处理数据
return data;
}
其实问题就在这个地方,当 query1()
执行后,连接池不够用了,其他请求进来,数据库连接被其他线程获取了,然后 query2()
就等待了,很快的一个方法就被挂起了,直接雪崩,系统的线程全被阻塞掉了。这里加一个只读事务,合并连接,重要的是不要出现这种 线程饥饿 的情况:
@Transactional(readOnly = true)
public Object fun() {
//code
}
后面测试,在能接受的延迟下性能提升了 4 倍,并发超过几倍后也只是响应变慢,不会出现整个系统阻塞。所以连接池只是暴露了一些代码的问题,最终还是要优化代码。
后记
多个线程通过 SynchronousQueue.pool()
获取连接,那么谁先获取到?
SynchronousQueue
初始化的时候可以选择公平或者非公平,Hikari 这里设置的公平模式,会按照等待线程的队列顺序分配连接。