featured image

问题

最近将一个项目的 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 这里设置的公平模式,会按照等待线程的队列顺序分配连接。

标记在:

, ,