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

3551 篇文章 120 订阅

背景

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

image.png

问题分析

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

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

sealed状态设置

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

image.png

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

image.png

另一个是获取连接时:

image.png

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

异常来源

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

image.png

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

异常原因

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

image.png

从日志中可以看到,我们前面两个数据源都正常初始化完成了(主从数据库),但是后面马上又报错了(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时间。

image.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值