线上问题处理案例:出乎意料的数据库连接池

2023-11-07

导读

本文是线上问题处理案例系列之一,旨在通过真实案例向读者介绍发现问题、定位问题、解决问题的方法。本文讲述了从垃圾回收耗时过长的表象,逐步定位到数据库连接池保活问题的全过程,并对其中用到的一些知识点进行了总结。

一、问题描述

大促期间,某接口超时次数增多,经排查直接原因是GC耗时过长,查看监控FullGC达500ms以上,接口超时时间与FullGC发生时间吻合。

图1 FullGC耗时监控

二、应用基本情况

  • 容器:8C12G;
  • JVM配置:-XX:+UseConcMarkSweepGC -Xms6144m -Xmx6144m -Xmn2048m -XX:ParallelGCThreads=8 -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -XX:+ParallelRefProcEnabled;
  • 数据库类型:MySQL;
  • 数据库连接池:DBCP;

三、排查过程

1、 GC耗时过长,说明内存中垃圾对象很多。

2、 首先怀疑是否有内存泄漏,观察FullGC后堆内存回收情况,尚属正常,暂时排除内存泄漏原因。

图2 发生FullGC后堆内存回收监控

3、 推断FullGC耗时过长是否因为老年代有大量死亡对象,遂导出FullGC前后堆内存dump,通过比对“保留大小”,发现FullGC后大量数据库相关对象被回收。

图3 堆内存对象分析

4、 数据库连接正常应该不会频繁创建和断开,进入老年代后,正常不应该被回收,通过堆dump内容OQL分析每个数据库连接数量,发现很多库连接数都大于“maxActive”数量,可以肯定有很多失效连接。

5、 初步判断直接原因是很多失效数据库连接进入老年代,导致FullGC耗时过长。

6、 怀疑连接池验证周期过长,导致数据库因空闲过长关闭连接,将连接池参数“
timeBetweenEvictionRunsMillis”由1分钟调整到10秒,问题依旧。

7、 阅读DBCP源码,发现是通过
org.apache.commons.pool.impl.GenericObjectPool.Evictor定时任务,按照timeBetweenEvictionRunsMillis配置的周期定时驱逐失效连接,驱逐条件:若连接空闲时间大于“minEvictableIdleTimeMillis”,则会驱逐连接,等待垃圾回收。若开启“testWhileIdle”则会执行“validationQuery”。进一步阅读代码,发现执行“validationQuery”后,连接空闲时间并不会重新计算,导致连接在业务低谷时很容易被淘汰,而数据库连接会关联大量对象,创建、回收成本昂贵,并且影响GC。

8、 反向思考,为何只有在大促期间才发生问题?

图4 平时和大促时回收频率对比

可以看到平时由于业务量小,GC不频繁,过期连接没有达到进入老年代阈值,在年轻代被回收。而大促时业务量大,GC频繁,连接在进入老年代以后才过期,导致老年代FullGC时间过长。

9、 至此,基本可以肯定问题原因是数据库连接池不具备“保活”能力,导致连接不断淘汰和新建,在业务高峰时段,连接进入老年代然后失效,造成FullGC耗时过长,最终导致接口超时次数增多。

四、解决方案

方案1:改为G1回收器,对老年代回收是分块进行,可以防止长时间停顿。另外默认MaxTenuringThreshold值是15,可以防止失效连接过早进入老年代;

方案2
minEvictableIdleTimeMillis设置为0,使数据库连接不会自动失效,进入老年代以后一直存活,避免在老年代失效回收;

五、问题总结

数据库连接池并不具备通常理解的“保活”能力,数据库连接在业务不活跃的应用中,会不断淘汰和重连,而连接会通过虚引用方式(
com.mysql.jdbc.NonRegisteringDriver$ConnectionPhantomReference)携带大量对象,如果连接存活时间内YGC次数达到寿命阈值,则会进入老年代,老年代是使用“标记-清除”算法,回收成本更高,进而造成FullGC耗时过长。

六、拓展知识点

1、 Druid连接池同样存在不能“保活”问题,较新版本提供“KeepAlive”选项(未验证);

2、 Druid连接池配置的“validationQuery”语句通常并不会被执行,MySqlValidConnectionChecker在检查连接有效性时,会判断驱动是否实现pingInternal方法,如果实现则会通过此方法验证有效性。MySQL的JDBC驱动实现了该方法,因此“validationQuery”配置的语句通常不会执行;

图5 连接有效性校验代码

3、 DBCP和Druid连接池默认都是FILO,如果业务不繁忙,会导致只有最前边的连接被使用-归还-使用,后边连接基本都在无谓的驱逐、重建连接;

4、 虚引用对GC的影响:这些引用只有经过两次GC才能被回收掉,如果进入老年代,则必须经过两次FullGC才能释放内存。本例中由于不断有新的虚引用对象在老年代失效,导致FullGC后,内存水位仍然偏高,会加剧GC压力。新版本JVM已对此做了优化,一次GC可以回收掉;

5、 类似的影响还有finalize方法;

6、 CMS回收器默认MaxTenuringThreshold为6,而ParallelGC和G1均默认15;

结语

本文对数据库连接失效引起的GC问题进行了详细分析,希望读者通过本文对数据库连接“保活”机制、GC问题基本分析方法有所收益,后续该系列文章会继续推出其他案例分享。

作者:京东零售 王利辉

内容来源:京东云开发者社区

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

线上问题处理案例:出乎意料的数据库连接池 的相关文章

随机推荐

  • TCP的半关闭状态以及tcp-keepalive

    文章目录 半关闭状态 实现方法 tcp keepalive 开启 tcp keepalive 方法1 Linux系统全局开启 方法2 setsockopt 设置 socket 半关闭状态 一次TCP四次挥手的过程如上图所示 设左侧为客户端
  • elasticsearch 去重查询并进行分页

    去重查询的俩种方式 在进行去重查询时 原来的目的是对于查询出的结果中一模一样的数据进行去重 但是各种百度发现都是对于单一字段的去重查询 最后索性新增了一个字段 将其他字段拼接了起来 从而根据拼接的字段进行去重查询 1 使用字段聚合 top
  • windows:windows10 下如何让程序被 Cortana搜索到

    参考 https blog csdn net qq 26462567 article details 101011871 总结 添加快捷方式到开始菜单目录
  • 测试框架pytest教程(5)运行失败用例-rerun failed tests

    content of test 50 py import pytest pytest mark parametrize i range 50 def test num i if i in 17 25 pytest fail bad luck
  • 八度音阶和频率的关

    八度音阶和频率的关系 Frequency in hertz semitones above or below middle C Octave Note 0 1 2 3 4 5 6 7 8 9 C 16 352 48 32 703 36 65
  • Qt 常用的字符转换,QString如何转换成const char类型, 转 PWCHAR wchar_t*

    常用的字符转换 日常记录 QString如何转换成const char类型 const char cmd data qstring toStdString c str qstring为待转换的qstring类型字符串 QString for
  • redis主从复制和哨兵模式

    redisi主从和哨兵模式 主从复制概扩及原理 redisi主从复制模式 redis哨兵原理 redis哨兵模式 主从复制概扩及原理 Redis主从复制是指将一个Redis实例 即主库 的数据复制到其他Redis实例 即从库 的过程 主节点
  • 一分钟了解HTTP和HTTPS协议

    很多人存在这样的疑惑就是http与https的区别 这篇文章就跟大家介绍一下 一句话总结HTTPS是身披SSL外壳的HTTP HTTPS更安全 实际使用中绝大多数的网站现在都采用的是HTTPS协议 这也是未来互联网发展的趋势 什么是协议 网
  • vue+elementui封装select-tree下拉树【单选

    组件代码
  • 鸟类识别扫一扫,AI识鸟一拍就知道鸟类信息

    随着工业化的发展 森林 湿地等生态系统逐步被开发 如何保护鸟类日益成为人们关注的话题 针对自然保护地鸟类监测面临的种类繁多 相似度高 活动范围大等痛点 快瞳科技研发上线的鸟类识别算法便致力于解决以上痛点 借助AI赋能助力生物多样性保护 快瞳
  • Linux命令之杀掉被占用的端口号

    前言 场景介绍 项目启动失败或者启动成功访问报404 很可能是访问端口被占用导致 记录一下杀死被占用端口的常用命令 共同学习 实现过程 方案1 查找被占用的端口号 netstat tln grep 8081 netstat anp grep
  • 关于Python中的可变对象与不可变对象的区别

    Python中的可变对象与不可变对象 什么是可变对象 不可变对象 可变对象 对象存放在地址中的值不会被改变 所谓的改变是创建了一块新的地址并把新的对象的值放在新地址中原来的对象并没有发生变化 不可变对象 对象存放在地址中的值会原地改变 in
  • 分享一个隐藏链接的样式

    先上效果图 再看代码
  • python3画直方图出现“Polygon‘ object has no property ‘normed”

    直方图原程序 import numpy as np import matplotlib pyplot as plt np random seed 0 mu sigma 100 20 均值和标准差 a np random normal mu
  • Linux下同一个Tomcat部署多个项目不同端口访问

    1 复制conf Catalina文件夹并命名为Catalina1 cd app tomcat8bi conf cp a Catalina Catalina1 2 复制webapps文件夹并命名为webapps1 cd app tomcat
  • openwrt--编译源码

    准备工作 首先 系统我用的是ubuntu2004 openwrt版本是github上最新版本的代码 安装需要的软件 sudo apt get install git g make libncurses5 dev subversion lib
  • H5网页等链接被微信秒封(拦截、屏蔽、和谐)后最好的解决方法

    H5网页等链接被微信秒封 拦截 屏蔽 和谐 后最好的解决方法 参考文章 1 H5网页等链接被微信秒封 拦截 屏蔽 和谐 后最好的解决方法 2 https www cnblogs com lkli p 11424598 html 备忘一下
  • Linux学习 day13之k8s基础简介

    k8s基础简介 一 Kubernetes 概述 开源的 用于管理云平台中多个主机上的容器化的应用 特点 Kubernetes 组件 Master 组件 提供集群管理控制中心 kube apiserver ETCD kube controll
  • COOKIE与SESSION的区别

    Web基础 COOKIE与SESSION的区别 一 COOKIE 1 COOKIE是什么 cookie可以理解是服务器暂存在客户端的文本信息 txt文件 2 COOKIE从哪来 cookie从服务端来 它是由服务端生成的 客户端可以清除co
  • 线上问题处理案例:出乎意料的数据库连接池

    导读 本文是线上问题处理案例系列之一 旨在通过真实案例向读者介绍发现问题 定位问题 解决问题的方法 本文讲述了从垃圾回收耗时过长的表象 逐步定位到数据库连接池保活问题的全过程 并对其中用到的一些知识点进行了总结 一 问题描述 大促期间 某接