数据库连接池提前初始化引发的异常

背景

我们线上一个项目在发版本时,经常会有部分实例无法正常启动的情况,特别是在没有进行灰度发布的场景下,基本上很难正常的启动成功,异常信息如下:

问题分析

看日志可以很容易看出来,启动异常的原因是hikari的配置绑定异常,原因是hikari的连接池已经seal了:The configuration of the pool is sealed once started.

字面意思:连接池配置一旦启动就被密封。那么问题来了,这个状态是怎么设置的?我的服务明明还没有启动;就算设置了,理论上配置初始化应该只会初始经一次,为什么会报错?

sealed状态设置

我们先看一下hikari的源码,找一下连接池的sealed状态是怎么设置的:

HikariConfig中查看设置sealed字段的方法,查看它的调用链路,发现只有两个地方调用了此方法来设置sealed状态为true,一个是构造函数,也就是初始化数据源时:

另一个是获取连接时:

可以看到,在HikariConfig构造函数中,会先初始化连接池,然后设置sealed状态。在getConnection时,会判断连接池是不是已经被初始化了,如果没有,则会初始化连接池,并设置sealed状态。

异常来源

我们现在知道sealed状态的设置时机,那么The configuration of the pool is sealed once started. 这个异常又是什么时候被抛出的呢,同样是在HikariConfig中搜索对应异常,可以看到这个异常都是通过checkIfSealed方法抛出,很明显,这个方法就是检查sealed状态的,查看它的调用链路:

可以看到,在设置HikariConfig的任意属性时,都会先检查状态。这么看来,sealed其实就是为了在连接池初始化完成之后,不允许再动态的去更新配置(可以使用其它的方式),那么理论上来说,Spring容器还在启动过程中, 为什么在设置属性之前,sealed状态就已经被设置的呢?

异常原因

我们回过去再去看一下启动日志,通过上面的源码,我们可以看到在初始化连接池的前后,都会打印INFO日志,那么我们可以搜索对应的日志查看连接池的初始化情况:

从日志中可以看到,我们前面两个数据源都正常初始化完成了(主从数据库),但是后面马上又报错了(Spring绑定属性),好像看不出什么来,但是如果我们关注一下日志打印对应的线程就可以发现,初始化连接池和报错的线程并不是同一个!前面的初始化日志,都是在dubbo线程中打印的,而后面报错的日志,是在main线程中。

这时候就很明显了,上面说到,sealed状态不仅在构造函数中设置,在getConnection时也会去初始化连接池并更新状态。这明显就是spring容器还在初始化中,但是dubbo服务已经提前暴露了,导致有请求进来开始请求DB了。

异常流程

  1. dubbo服务成功暴露
  2. dubbo请求进入,存在DB请求,调用getConnection初始化连接时,sealed = true
  3. spring容器创建数据源,设置属性时发现sealed = true,抛出异常
  4. 服务启动失败

回到最开始的现象,特别是在没有进行灰度发布的场景下,基本上很难正常的启动成功 。在灰度发布时,是服务启动后,才会转发部分流量到灰度容器,所以dubbo是否提前暴露并没有影响,因为在启动过程中并没有请求进入。但是在非灰度发布场景下,由于dubbo服务一暴露,马上就有请求进入,所以导致启动异常。

解决

知道问题产生的原因,就很容易解决问题了,解决方式很简单,让dubbo的服务在spring容器启动后暴露即可,从dubbo的文档可以看到,2.6.5之后不会再出现这种弱智问题了,但是我们的服务比较旧,用的2.6.3的版本,所以需要配置delay=-1,或者设置一个较长的delay时间。

相关推荐
952361 小时前
MyBatis
后端·spring·mybatis
FQNmxDG4S3 小时前
Java多线程编程:Thread与Runnable的并发控制
java·开发语言
虹科网络安全4 小时前
艾体宝干货|数据复制详解:类型、原理与适用场景
java·开发语言·数据库
axng pmje4 小时前
Java语法进阶
java·开发语言·jvm
uzong4 小时前
9 种 RAG 架构,每位 AI 开发者必学:完整实战指南
后端
rKWP8gKv74 小时前
Java微服务性能监控:Prometheus与Grafana集成方案
java·微服务·prometheus
老前端的功夫4 小时前
【Java从入门到入土】28:Stream API:告别for循环的新时代
java·开发语言·python
qq_435287924 小时前
第9章 夸父逐日与后羿射日:死循环与进程终止?十个太阳同时值班的并行冲突
java·开发语言·git·死循环·进程终止·并行冲突·夸父逐日
小江的记录本4 小时前
【Kafka核心】架构模型:Producer、Broker、Consumer、Consumer Group、Topic、Partition、Replica
java·数据库·分布式·后端·搜索引擎·架构·kafka
止语Lab4 小时前
从手动到框架:Go DI 演进的三个拐点
开发语言·后端·golang