《故障复盘 · 数据库连接异常关闭》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

CSDN.gif

写在前面的话

博主所在公司的产品线,部署上线了多家客户,遇到的线上故障的场景也较多,这边继续更新一下故障复盘系列,记录并分享一下这些故障的的定位、分析、解决过程。
这里分享的这篇,是由于Druid连接异常关闭,进而引发大范围故障的问题。


问题背景

某天下午,被领导拉到一个群,要求协助开发排查一个由于前一天死循环的问题,导致积累了几天的未申请医嘱记录,数据量较大。最终导致定时器执行“医嘱自动申请”接口的时候,由于执行时间过长,进而报错,错误如下:
image.png

Tips:开局一张图,剩下全靠查。


问题分析

1、该报错的文字版:
Could not commit JDBC transaction; nested exception is java.sql.SQLException: Connection closed.

2、该错误的翻译版:
无法提交JDBC事务,异常原因是连接已关闭

3、继续解释一下:
应该是由于某个事务逻辑执行时间较长,导致提交事务的时候,连接已经被关闭了,无法提交。

4、和开发讨论后得出该医嘱自动申请的逻辑大致如下:
医嘱自动申请接口,在整体逻辑上有特殊性,先是取到一个医嘱列表List,然后遍历处理每条医嘱记录,这里的某条医嘱记录称呼为A,A记录处理的时候又会查出子列表,这里的某个子列表里面的数据称之为B,然后会新开事务去处理子逻辑B,因此导致A的耗时可能较长,又因为B都是新开事务,可能导致A的连接一直空闲,啥事没做。


问题排查

由于手头事情较多,暂无时间拉取实际代码测试。
那先直接从错误查起,既然说连接被关闭,那就查一下数据源等配置。
从项目现场Nacos拷贝了数据库连接池配置,
和连接数量相关的属性,可以看出最大连接是150,获取连接等待超时60秒,但该报错明显不是连接池满,以及获取连接超时。
继续看,和连接关闭的属性相关的,无非:
minEvictableIdleTimeMillis(300秒)
remove-abandoned(开启)
removeAbandonedTimeoutMillis(80秒)

database:
  pool:
    default:
      # 最大连接池数量
      max-active: 150
      # 从连接池获取连接等待超时的时间
      max-wait: 60000
      # 最小连接池数量
      min-idle: 1
      # 配置一个连接在池中最大空闲时间,单位是毫秒
      min-evictable-idle-time-millis: 300000
      # 连接泄露检查,打开removeAbandoned功能 , 连接从连接池借出后,长时间不归还,将触发强制回连接。回收周期随timeBetweenEvictionRunsMillis进行,如果连接为从连接池借出状态,并且未执行任何sql,并且从借出时间起已超过removeAbandonedTimeout时间,则强制归还连接到连接池中。
      remove-abandoned: true
      # 回收超时时间
      remove-abandoned-timeout-millis: 80000
      # 打开后,增强timeBetweenEvictionRunsMillis的周期性连接检查,minIdle内的空闲连接,每次检查强制验证连接有效性. 参考:https://github.com/alibaba/druid/wiki/KeepAlive_cn
      keep-alive: true
      # 打开PSCache
      pool-prepared-statements: true
      # 指定每个连接上PSCache的大小,Oracle等支持游标的数据库,打开此开关,会以数量级提升性能,具体查阅PSCache相关资料
      max-pool-prepared-statement-per-connection-size: 20

【知识补充】
Druid 部分属性说明:
1、minEvictableIdleTimeMillis:最小空闲时间,默认30分钟,如果连接池中非运行中的连接数大于minIdle,并且那部分连接的非运行时间大于minEvictableIdleTimeMillis,则连接池会将那部分连接设置成Idle状态并关闭;也就是说如果一条连接30分钟都没有使用到,并且这种连接的数量超过了minIdle,则这些连接就会被关闭了。
**2、removeAbandonedTimeoutMillis:**检查连接泄露依据(超时时间),默认5分钟,连接回收的超时时间;设置了removeAbandoned为true,Druid会定期检查线程池溢出的情况,如果不是运行状态,且超过设置的时间就会被回收;
**3、removeAbandoned:**代表是否清理removeAbandonedTimeout秒没有使用的活动连接,清理后并没有放回连接池
4、logAbandoned:连接池收回空闲的活动连接时是否打印消息

Tips:minEvictableIdleTimeMillis 与 removeAbandonedTimeout 这两个参数针对的连接对象不一样,前者针对连接池中的连接对象,后者针对未被close的活动连接,因此针对业务处理逻辑太长,比较重要的还是后者。

【领导继续提问,为啥其他医院量有时候也很大,又正常?】
有什么好说的?那上这家医院的配置?如下所示:
这家医院是旧架构,即SSM框架。可以看到removeAbandonedTimeout配置了900秒,同时removeAbandoned是false,即为不检查。

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass -->
    <property name="driverClassName" value="${datasource.driver}"/>
    <!-- 基本属性 url、user、password -->    <property name="url" value="${datasource.url}"/>
    <property name="username" value="${datasource.username}"/>
    <property name="password" value="${datasource.password}"/>
    <!-- 配置初始化大小、最小、最大 -->
    <property name="initialSize" value="${datasource.initialSize}"/>
    <property name="minIdle" value="${datasource.minIdle}"/>
    <property name="maxActive" value="${datasource.maxActive}"/>    <!-- 配置获取连接等待超时的时间 -->
    <property name="maxWait" value="5000"/>    <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="60000"/>    <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="300000"/>    <property name="validationQuery" value="${jdbc.testSql}"/>
    <property name="testWhileIdle" value="true"/>
    <property name="testOnBorrow" value="false"/>
    <property name="testOnReturn" value="false"/>    <!-- 长时间不使用的连接强制关闭 -->
    <property name="removeAbandoned" value="false"/>
    <!-- 配置开始关闭空闲连接的时间,单位是秒 -->
    <property name="removeAbandonedTimeout" value="900"/>
    <!-- 是否将关闭操作进行日志打印 -->
    <property name="logAbandoned" value="false"/>
    <!--保证连接池中的连接是真实有效的连接,并且空闲连接数维持为minIdle-->
    <property name="keepAlive" value="true"/> 
    <property name="filters" value="stat,config"/>
    <property name="connectionProperties" value="config.decrypt=true;config.decrypt.key=${publickey}" />
</bean>

问题结果

鉴于医嘱的逻辑可能涉及较长时间,因此,80秒可能不够,建议改大。
removeAbandoned 属性依然建议打开,否则会增加连接池泄露风险。


知识拓展

【错误模拟】
晚上有点时间,就想来模拟一下,口说无凭。
其实本地很好重现这个错误,准备如下代码和配置:
1、事务代码里面执行一段更新语法,阻塞一段时间;
2、设置间隔2秒检查,回收打开,超时3秒;
image.png
image.png
可以看到报错如下:
image.png

【源码相关】
image.png
image.png


总结陈词

此篇文章介绍了连接异常关闭 的故障复盘,仅供学习参考。
出现问题并不可靠,主要是能从问题中总结出什么东西,这些不断积累的过程才是令人兴奋的。

Tips:青海长云暗雪山,孤城遥望玉门关。黄沙百战穿金甲,不破楼兰终不还。

CSDN_END.gif

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

战神刘玉栋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值