
问题
最近将一个项目的 SpringBoot 1.xx 升级到了 SpringBoot 2.xx,顺便把 JDK 也升级到了 15,测试了下没什么问题就发到了生产,但今天项目报错了,HikariPool-1 - Connection is not available, request timed out after 30000ms,而且很快系统就被拖垮了,具体的一些错误如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| 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]
...
|
解决方法
在网上搜索了一番,很多人都说增加数据库连接池可以解决。确实如此,根据生产的并发量大概计算一下需要多少个连接,修改配置就可以了,比如我的配置:
1
2
3
4
| spring:
datasource:
hikari:
maximum-pool-size: 20
|
这样也只是解决了部分问题,当压测的时候,并发调到 21 的时候,还是容易出现上述问题,后面根据 这篇文章 的思路找到了问题的所在。
通过错误日志看到错误总是在一个方法的两个地方出错,但这个方法很普通,简单的数据库查询,返回值,类似于:
1
2
3
4
5
6
7
| public Object fun() {
Object data1 = dao.query1();
//...处理数据
Object data2 = dao.query2();
//...处理数据
return data;
}
|
其实问题就在这个地方,当 query1()
执行后,连接池不够用了,其他请求进来,数据库连接被其他线程获取了,然后 query2()
就等待了,很快的一个方法就被挂起了,直接雪崩,系统的线程全被阻塞掉了。这里加一个只读事务,合并连接,重要的是不要出现这种 线程饥饿 的情况:
1
2
3
4
| @Transactional(readOnly = true)
public Object fun() {
//code
}
|
后面测试,在能接受的延迟下性能提升了 4 倍,并发超过几倍后也只是响应变慢,不会出现整个系统阻塞。所以连接池只是暴露了一些代码的问题,最终还是要优化代码。
后记
多个线程通过 SynchronousQueue.pool()
获取连接,那么谁先获取到?
SynchronousQueue
初始化的时候可以选择公平或者非公平,Hikari 这里设置的公平模式,会按照等待线程的队列顺序分配连接。