featured image

问题

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