故障复盘:数据库连接池
本文来源:http://r6d.cn/9waY
本文主要描述几次我司碰到的由于数据库连接池遇到的故障。上半部分描述了几个具体的案例,最后对数据库连接池导致的故障问题进行了总结,以便未来应对类似问题能够快速定位。
故障1:防火墙
本次故障发生于18年初。故障表现为,在生产环境中,某些应用偶尔会出现服务停止的现象。重启以后问题解决,但比较长的一段时间后又出现了该问题。为了重现该问题,必须等到系统度过了一个没有加班的周末可能才会出现。耗费了团队数周的时间。问题解决小组一度没有思绪。
问题追踪:采用了以下方法进行故障的追踪
-
jstat,jinfo,jmap等命令,查看堆栈信息,查看线程,定位代码 -
查看错误日志 -
查看系统配置 -
查看cat异常日志
通过java bin中的小工具,只查到了系统在数据库访问的线程中存在挂起状态,无法定位到某些固定的方法中,任意选取了一个报错的方法,走查代码没有发现任何问题。怀疑问题【不是】由于某一段业务代码或者dao层代码的逻辑导致,而是由于连接池配置导致。进而,查找了相关数据库连接池(druid)的官方文档,按照官方文档和搜索到的通用解决方案,进行了druid的参数配置,包括testOnReturn等各种参数的尝试。修改完成后,系统上线。
一周以后,问题又出来了。进一步检查各个业务代码,发现公司除了使用druid连接池以外,还使用了tomcat的连接池或者其他的连接池。作为架构组的一员,顿时就怀疑是业务组胡乱使用连接池(赶紧甩锅!),导致引入了连接池本身的bug造成了故障(并没有通过连接池源代码进行分析)。于是乎,大面积替换了各个业务线原有的连接池组件,替换为druid。并且进一步咨询DBA,参数配置是否合理,DBA也掏出他线上稳定运行的项目进行了参数的对比,确认无误,于是满怀细心的又部署上服务器了。
几天以后,问题又出来了。于是乎,怀疑是mysql的wait_timeout参数问题,该参数在mysql中默认是8小时,一旦达到8小时主动杀连接,于是乎,尝试修改该参数到7天,查看问题是否仍然存在。
一周以后,问题又出来了。故障上升了一个层次,由公司某领导过问,于是乎,DBA,开发,架构,测试,运维成立了问题小组,针对该问题进行复现和定位。不解决不回家。于是乎,我配合测试团队进行该故障的压力测试,尝试进行问题的重现。同时,dba查看mysql连接信息,运维排查服务器各项指标和网络联通。这样持续了几个小时以后… 问题依然没有重现!大家都要疯了。吃完几块饼干,挑灯夜战了。停止了压力测试,重新开始分析问题(其实还是在蒙)。又经过一轮的连接池参数调节,日志追踪,jvisualvm等,各种手段的折腾,又蒙了一版,准备部署上去看。这个时候测试人员习惯性的调用了一次接口。“太好了!挂了!”于是乎,又是一波骚操作,但是很可惜还是没有找到问题。解决的方法还是那些方法,各种jvm折腾,监控折腾,撸代码撸日志,远程debug打断点。各种搞不定。
最后,新来的一个DBA小哥提出一个他曾经碰到的坑,是由于他们公司环境的防火墙还有一个超时没有流量自动屏蔽连接信息(类似于移除iptables的操作),此时,应用服务器到数据库服务器的连通性出现问题,导致连接被断掉。大家一听,有道理,于是乎给服务器加了一个心跳。
问题解决了。解决方法还是靠蒙的,并没有什么真正定位到问题:)
故障2:mysql8小时故障
由于我们公司的业务特性,并不会有太多流量持续在跑,druid连接池的配置按照常规配置。但是仍然有的应用运行几天以后,出现了数据库连接超时的问题。查看日志,发现刚好8小时。直接就“蒙”问题原因是mysql的wait_timeout参数和druid的参数在某些情况下刚好出现了死连接的问题。由于数据库连接池中,每次只会死掉一部分连接,在开发的过程中,第一次失败了,第二次可能就成功了,所以扛到了连接池中出现了大量死连接才暴露出问题。
我们的解决方法是通过调节druid参数可以解决。当然也可以通过修改mysql的wait_timeout参数配置临时解决。其实还可以通过修改druid的minIdel参数,这个参数其实是可以非常小的,我们的业务场景中并不需要进行连接的“预热”操作,没有连接了创建一个就好了,偶尔创建连接的开销在我们的低效代码下,不是问题瓶颈啦。
故障3:nodejs的连接池的坑
公司的rap2是nodejs开发的,数据库连接池采用的nodejs的orm框架sequelize自带的pool。现象时运行一段时间,就有同事反馈rap2非常之慢。于是我马上打开一个网页,多次刷新,发现时而超时,时而正常。通过加日志,升级rap2,升级sequelize等方法后,重新构建部署。问题依然存在。此时,重启应用以后,问题解决,速度飞起。
一周以后,问题又出来了。但是通过刷新以后,页面通常就正常了,所以给大家的解决方案是:多次尝试刷新页面。但是我是知道问题仍然存在的…于是rap2就在大家不停的刷新下,艰难的服务着。
最终将问题反馈给rap2作者,并且阅读了sequelize相关的所有配置和参数,尝试进行了修复,无奈问题依然没有解决。确实不知道nodejs中如何像druid这样配置参数。至今问题依然存在,每过几周就需要找运维帮忙杀掉pod重启应用。只是尝试了将mysql的wait_timeout修改为3天,问题可以有所缓解,但是依然只能无止尽的reboot。如果你有该方面的解决方案,欢迎联系我。
进一步分析该问题,有可能是因为引入了redis缓存,大量的请求并不会将流量发到数据库中。后期可能会针对定期清理redis缓存、修改数据库连接池的minIdel参数等曲线救国的方法处理。
总结
总结起来数据库连接池的坑还是在于连接池组件本身。当数据库连接的中断不是由连接池组件发起时,连接池无法知晓该状态,最终导致了“连接泄漏”的情况。
-
数据库中断可能在多个环节产生:mysql,服务器,路由,防火墙,网络等等。任何环节都可能主动杀掉连接。 -
mysql8小时wait_timeout是一个经常碰到问题。 -
公司的底层框架针对该问题,尝试进行了处理:当任何一个连接失败后,控制连接池组件主动断掉所有的连接。
如果资源对你有帮助的话
❤️ 给个 「在看」 ,是最大的支持❤️