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

问题,处理,案例,出乎意料,数据库,连接池 · 浏览次数 : 493

小编点评

**问题描述** 在大促期间,某接口超时次数增多,经排查直接原因是GC耗时过长。图1显示,GC耗时超过了500ms。 **应用基本情况** * 容器:8C12G * JVM配置:-XX:+UseConcMarkSweepGC -Xms6144m -Xmx6144m -Xmn2048m -XX:ParallelGCThreads=8 -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -XX:+ParallelRefProcEnabled * 数据库类型:MySQL * 数据库连接池:DBCP * 连接池参数:timeBetweenEvictionRunsMillis **排查过程** 1. GC耗时过长说明内存中垃圾对象很多。 2. 首先怀疑内存泄漏,观察FullGC后堆内存回收情况,尚属正常,暂时排除内存泄漏原因。 3. 推断FullGC耗时过长是否因为老年代有大量死亡对象,遂导出FullGC前后堆内存dump,通过比对“保留大小”,发现FullGC后大量数据库相关对象被回收。 4. 初步判断直接原因是很多失效数据库连接进入老年代,导致FullGC耗时过长。 5. 怀疑连接池验证周期过长,导致数据库因空闲过长关闭连接,将连接池参数“timeBetweenEvictionRunsMillis”由1分钟调整到10秒,问题依旧。 6. 阅读DBCP源码,发现是通过org.apache.commons.pool.impl.GenericObjectPool.Evictor定时任务,按照timeBetweenEvictionRunsMillis配置的周期定时驱逐失效连接,驱逐条件:若连接空闲时间大于“minEvictableIdleTimeMillis”,则会驱逐连接,等待垃圾回收。若开启“testWhileIdle”则会执行“validationQuery”。 7. 进一步阅读代码,发现执行“validationQuery”后,连接空闲时间并不会重新计算,导致连接在业务低谷时容易被淘汰,而数据库连接会关联大量对象,创建、回收成本昂贵,并且影响GC。 8. 反向思考,为何只有在大促期间才发生问题? * 图4平时和大促时回收频率对比可以看到平时由于业务量小,GC不频繁,过期连接没有达到进入老年代阈值,在年轻代被回收。 * 而大促时业务量大,GC频繁,连接在进入老年代以后才过期,导致老年代FullGC时间过长。 **解决方案方案** 1. 改为G1回收器,对老年代回收是分块进行,可以防止长时间停顿。 2. minEvictableIdleTimeMillis设置为0,使数据库连接不会自动失效,进入老年代以后一直存活,避免在老年代失效回收。 3. 使用“keepAlive”选项解决连接失效问题,并设置连接池参数“testWhileIdle”以确保连接在业务低谷时仍然活跃。 **问题总结** 数据库连接池并不具备通常理解的“保活”能力,数据库连接在业务不活跃的应用中,会不断淘汰和重连,而连接会通过虚引用方式(com.mysql.jdbc.NonRegisteringDriver$ConnectionPhantomReference)携带大量对象,如果连接存活时间内YGC次数达到寿命阈值,则会进入老年代,老年代是使用“标记-清除”算法,回收成本更高,进而造成FullGC耗时过长。

正文

导读

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

一、问题描述

大促期间,某接口超时次数增多,经排查直接原因是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问题基本分析方法有所收益,后续该系列文章会继续推出其他案例分享。

作者:京东零售 王利辉

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

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

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

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

【产研测类】线上问题处理机制

1 概述 本规范致力于优化运营与产研团队在线问题管理的效率与效果,全面覆盖生产问题的识别、处理机制、分类分级、责任归属和明确奖惩机制。同时,侧重资源重点解决主流程关联的核心模块生产问题。如此,确保各个环节责任到人,内容详实,助力团队高效协同。 2 线上问题 2.1 线上问题定义 在互联网产品研发、运

[转帖]线上Java 高CPU占用、高内存占用排查思路

一、前言 处理过线上问题的同学基本上都会遇到系统突然运行缓慢,CPU 100%,以及Full GC次数过多的问题。当然,这些问题的最终导致的直观现象就是系统运行缓慢,并且有大量的报警。本文主要针对系统运行缓慢这一问题,提供该问题的排查思路,从而定位出问题的代码点,进而提供解决该问题的思路。 二、分析

基于ClickHouse解决活动海量数据问题

魔笛活动平台要记录每个活动的用户行为数据,帮助客服、运营、产品、研发等快速处理客诉、解决线上问题并进行相关数据分析和报警。可以预见到需要存储和分析海量数据,预估至少几十亿甚至上百亿的数据量,所以需要选择一款能存储海量数据的数据库。由于是通过接收MQ存储或者API方式存储,所以对实时写入性能也有一定要求

MySQL innoDB 间隙锁产生的死锁问题

线上经常偶发死锁问题,当时处理一张表,也没有联表处理,但是有两个mq入口,并且消息体存在一样的情况,频率还不是很低,这么一个背景,我非常容易怀疑到,两个消息同时近到这一个事务里面导致的,但是是偶发的,又模拟不出来什么场景会导致死锁,只能进行代码分析,问题还原的方式去排查问题。

[转帖]nginx的proxy_next_upstream使用中的一个坑

https://zhuanlan.zhihu.com/p/35803906 今天线上系统出了点问题,机房的电信出口突然不通了,原本以为能自动切换的nginx配置,居然没有生效,导致了业务告警,手工紧急处理了才解决了。 当时的设想是,如果这个服务的访问,出现了500或者超时的情况,会自动重试到下一个服

让IIS支持.NET Web Api PUT和DELETE请求

前言 有很长一段时间没有使用过IIS来托管应用了,今天用IIS来托管一个比较老的.NET Fx4.6的项目。发布到线上后居然一直调用不同本地却一直是正常的,关键是POST和GET请求都是正常的,只有PUT和DELETE请求是有问题的。经过一番思考忽然想起来了IIS默认情况下拒绝处理PUT和DELET

记一次 Redisson 线上问题 → ERR unknown command 'WAIT' 的排查与分析

开心一刻 昨晚和一个朋友聊天 我:处对象吗,咱俩试试? 朋友:我有对象 我:我不信,有对象不公开? 朋友:不好公开,我当的小三 问题背景 程序在生产环境稳定的跑着 直到有一天,公司执行组件漏洞扫描,有漏洞的 jar 要进行升级修复 然后我就按着扫描报告将有漏洞的 jar 修复到指定的版本 自己在开发

线上问题排查--进程重启失败,最后发现是忘了cd

# 背景 我前面写了几篇文章,讲c3p0数据库连接池发生了连接泄露,但是随机出现,难以确定根因,最终呢,为了快速解决问题,我是先写了个shell脚本,脚本主要是检测服务的接口访问日志,看看过去的30s内是不是接口几乎都超时了,如果是的话,咱们就重启服务。然后把这个shell加入到了crontab里,

线上FullGC问题排查实践——手把手教你排查线上问题

作者:京东科技 韩国凯 一、问题发现与排查 1.1 找到问题原因 问题起因是我们收到了jdos的容器CPU告警,CPU使用率已经达到104% 观察该机器日志发现,此时有很多线程在执行跑批任务。正常来说,跑批任务是低CPU高内存型,所以此时考虑是FullGC引起的大量CPU占用(之前有类似情况,告知用