HikariPool连接池报错(Possibly consider using a shorter maxLifetime value)

1、错误信息

com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@xxxxxxx (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value

在解决错误之前,先理解下HikariPool的相关配置内容:

1.1、认识HikariPool的结构

包括以下几个部分:

    1. ConcurrentBag & PoolEntry
    1. addConnectionExecutor
    1. houseKeeperTask
    1. HikariProxyConnection
    1. closeConnectionExecutor

1.2、ConcurrentBag

直译就是“连接口袋”,就是放数据库连接的口袋,也就是连接池。

ConcurrentBag有3个保存数据库连接的地方(池):

  1. threadList:是一个ThreadLocal变量,保存当前线程持有的数据库连接。
  2. sharedList:是一个CopyOnWriteArrayList,线程安全的arrayList,数据库连接创建后首先进入sharedList。
  3. handoffQueue:是一个SynchronousQueue,数据库连接创建、进入sharedList后,也会进入handoffQueue等待连接请求。

除此之外,ConcurrentBag还有一个比较重要的属性waiters,记录向连接池获取数据库连接而不得、等待的线程数。

1.3、PoolEntry

连接池中存储的对象不是Connection,也不是Connection的代理对象,而是PoolEntry。

PoolEntry持有数据库连接对象Connection,Connection在实例化PoolEntry前创建。PoolEntry创建的过程中会同时创建两个ScheduledFuture任务:endOfLife和keepalive。

endOfLife提交MaxLifetimeTask定时任务,在PoolEntry创建后的maxLifetime(参数指定)执行。MaxLifetimeTask会关闭当前连接、同时根据需要创建新的连接加入到连接池。

keepalive提交KeepaliveTask周期性任务,在PoolEntry创建后按照keepalive(参数设定)周期性执行,在当前连接空闲的情况下检查连接是否可用(使用参数指定的ConnectionTestQuery进行测试),如果连接不可用则关闭并重新创建连接。

1.4、addConnectionExecutor

添加数据库连接的任务管理器。负责数据库连接的创建以及加入到连接池中(sharedList和handoffQueue)。

注意addConnectionExecutor本身是一个线程池ThreadPoolExecutor,线程池容量为最大数据库连接数,HikariPool初始化的过程中会以多线程(corePoolSize=CPU内核数量)的方式快速完成连接创建和池化,之后正常情况下会以单线程(corePoolSize=1)的方式创建数据库连接。

houseKeeperTask

是一个周期性线程池ScheduledExecutorService,初始化的时候创建,定时(housekeepingPeriodMs)执行,清理掉(关闭)连接池中多余的(大于最小空闲数)空闲连接,如果连接池中的连接数没有达到参数设置的数量(最大连接数、或者空闲连接没有达到最小空闲连接数)则创建连接。

1.5、HikariProxyConnection

HikariProxyConnection是数据库连接Connection的扩展类,应用层通过getConnection方法获取到的数据库连接其实不是不是数据库连接Connection、而是这个扩展类HikariProxyConnection。

使用扩展类HikariProxyConnection、而不是原生的数据库连接Connection的一个最直观的理由是,对于数据库连接池来说,连接的关闭方法close不是要关闭连接、而是要把连接交还给连接池。使用扩展类(或者代理类)就可以很容易的重写其close方法实现目标,而如果直接使用原生Connection的话,就没办法控制了。

closeConnectionExecutor

数据库连接池需要关闭的时候,通过closeConnectionExecutor线程池提交,关闭连接后closeConnectionExecutor还负责调用fillPool(),根据需要填充连接池。

2、看下一般的配置

此错误提示看上去像是在说 maxLifetime 设置过长了,在HikariPool内部有一个规则,就是maxLifetime的设置时间要小于数据库的timeout,所以排查下maxLifetime和timeout的值

2.1、目前我的配置

  • yml配置
 datasource:
   hikari:
      max-lifetime: 1800000  # 设置为 30 分钟(比 MySQL 默认空闲超时时间短)
      connection-test-query: SELECT 1
      leak-detection-threshold: 2100 # 2秒检测连接泄露
      connection-timeout: 30000  # 30秒的连接超时
      idle-timeout: 600000  # 10分钟的空闲超时
  • mysql配置 mysql有关连接超时的参数有两个,一个是interactive_timeout 另一个是wait_timeout ,单位都是s
show variables like '%timeout%';
Variable_name value
interactive_timeout 7200
wait_timeout 7200

可以看出 max-lifetime 远小于 数据库的timeout时间,并且idle-timeout也小于max-lifetime配置

最终经过多方的源码翻看,原来需要再配置一个参数

datasource:
  hikari:
    keepalive-time: 509999

keepalive-time小于idle-timeout就行

3、总结

核心参数配置 数据库timeout > hikari.max-lifetime > hikari.idle-timeout > hikari.keepalive-time 完美解决问题

补充:4、Hikari连接池高性能的原因

  • 1、采用自定义的FastList替代了ArrayList,FastList的get方法去除了范围检查rangeCheck逻辑,并且remove方法是从尾部开始扫描的,而并不是从头部开始扫描的。因为Connection的打开和关闭顺序通常是相反的

  • 2、初始化时创建了两个HikariPool对象,一个采用final类型定义,避免在获取连接时才初始化,因为获取连接时才初始化就需要做同步处理

  • 3、Hikari创建连接是通过javassist动态字节码生成技术创建的,性能更好

  • 4、从连接池中获取连接时对于同一个线程在threadLocal中添加了缓存,同一线程获取连接时没有并发操作

  • 5、Hikari最大的特点是在高并发的情况下尽量的减少锁竞争

end
  • 作者:lishe (联系作者)
  • 发表时间:2024-10-18 10:52
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 转载声明:如果是转载博主转载的文章,请附上原文链接
  • 公众号转载:请在文末添加作者公众号二维码(公众号二维码见右边,欢迎关注)
  • 评论