你们好,我是鸭血粉丝(你们会亲切的喊我 「阿粉」),是一位喜欢吃鸭血粉丝的程序员,回想起以前线上出现 OOM 的场景,毕竟当时是第一次遇到这么 紧脏 的大事,要好好记录下来。java
在某次周五,经过 Grafana 监控,发现线上环境忽然出现CPU和内存飙升的状况:git
可是看到网络输出和输入流量都不是很高,因此网站被别人攻击的几率不高,后来其它服务器的负荷居高不下。程序员
阿粉先 dump 下当时的堆栈信息,保留现场,接着进行了简单的分析,为了稳住用户,通知运维一台一台服务器进行从新启动,让你们继续使用服务。正则表达式
接着就开始分析和回顾事情了shell
建议你们了解一些经常使用的 Linux 语法,例如 Grep
查询命令,是日志分析的一大利器,还能经过正则表达式查询更多内容。数据库
既然服务器在某个时间点出现了高负荷,因而就先去找一开始出现问题的服务器,去找耗时比较长的服务,例如我当时去找数据库耗时的服务,因为发生 OOM 时的日志已经被刷掉,因而我大体描述一下:性能优化
[admin@xxx xxxyyyy]$ grep '15:14:' common-dal-digest.log | grep -E '[0-9]{4,}ms' 2018-08-25 15:14:21,656 - [(xxxxMapper,getXXXListByParams,Y,1089ms)](traceId=5da451277e14418abf5eea18fd2b61bf)
很明显,上述语句是 查询在15:14那一分钟内,在common-dal-digest.log文件中,耗时超过1000ms的SQL服务(查的是耗时超过10秒的服务)。服务器
日志中有个特殊的标志 traceId,在请求链路中是惟一的,因此根据这个标志能分析单请求的全链路操做,建议你们的日志框架中也加上这种字段,让服务可追溯和排查。网络
经过 traceId去查 http 保存的访问日志,定位在该时间点内,分发到该服务器上的用户请求。还有根据该traceId,定位到整个调用流程所使用到的服务,发现的确十分耗时...app
因而拿到了该请求具体信息,包括用户的登陆手机号码,由于这个时候,其它几台服务器也出现了 CPU 和内存负载升高,因而根据手机号查询了其它几台服务器的访问日志,发现同一个请求,该用户也调用了不少次...
因而初步确认了某个耗时接口
官方介绍:
MAT
是Memory Analyzer
的简称,它是一款功能强大的 Java 堆内存分析器。能够用于查找内存泄露以及查看内存消耗状况。MAT 是基于Eclipse开发的,是一款免费的性能分析工具。读者能够在 http://www.eclipse.org/mat/ 下载并使用 MAT。
在前面提到,出现问题时,顺手保存了一份堆栈信息,使用工具打开后,效果图以下所示:
整个应用的内存大小 1.6G,而后有一块内存块居然占用了 1.4G,占比达到了 87.5%,这也太离谱了吧!
因而阿粉决定好好分析该对象的引用树,右键选择【class_reference】,查看对象列表,和观察 GC 日志,定位到具体的对象信息。
经过日志分析以及 dump 文件分析,都指向了某个文件导出接口,接着在代码中分析该接口具体调用链路,发现导出的数据不少,并且老代码进行计算的逻辑嵌套了不少 for 循环,并且是 for 循环调用数据库,计算效率极低。
模拟了该用户在这个接口的所调用数据量,须要查询多个表,而后 for 循环中大概会计算个 100w+ 次,致使阻塞了其它请求,因为请求未结束,java 对象没法被 GC 回收,线上的服务器 CPU 和内存使用状况一直飙升。
点开该段代码的 git 提交记录,发现是我在实习期写的时候,个人心里是崩溃的,当时对业务不熟悉,直接循环调用了老代码,并且也没有测试过这么大的数据量,因此 GG了。
而后我就开始作代码性能优化,首先仔细梳理了一下整个业务流程,经过增长 SQL 查询条件,减小数据库 IO 和查询返回的数据量,优化判断条件,减小 for 嵌套、循环次数和计算量。
官方描述:
VisualVM
,可以监控线程,内存状况,查看方法的CPU时间和内存中的对象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的).
该插件不须要另外下载,在 ${JAVA_HOME}/bin
目录下就能找到,因此安装了 jdk 就能使用它~
对比了新老代码所占用的 CPU 时间和内存状态
优化前:
优化后:
经过上述优化以后,计算 1w 条数据量,进行导出成报表文件,在老代码须要 48s,新代码降低到了 8s,不过这是大数据量的状况下,实际用户的数据没有这么多,因此基本上知足了线上 99% 的用户使用。(看到这个结果时,阿粉露出了「纯洁的微笑」)
固然,因为这些数据是本地开发环境新增长的,与出现 OOM 问题的用户数据量还有些差异,但经过优化后的代码,已经在数据库查询的时候就过滤掉不少无效的数据,在 for 循环计算前也加了过滤条件,因此真正计算起来起来就下降了不少计算量!
恩,本身代码优化好了,还要等测试爸爸们测试后才敢上线,此次要疯狂造数据!
阿粉周末会本身作点饭🍚,喜欢看王刚老师的视频,以为最后出现的 「技术总结」 很棒,让我可以快速记住重要步骤
(但学习跟作饭同样,仍是得通过反复看和实践才能好好记住,哼儿哈儿,大伙知道阿粉想说的是啥了吧哈哈哈)
在开发初期,阿粉没有考虑到性能问题,想着知足需求就完成任务,但数据量一大起来,就有可能出现这些 OOM 问题,因此之后开发时,须要先提早考虑如下几点:
出现问题时也不要惊慌,好好去解决它才是王道,「在解决问题中学到更多技术」
能够参考如下步骤:
在定位到问题时,看到是阿粉写的,本来觉得会受到批评,但领导并无责怪我,还叫阿粉以后好好改,避免下次出现这种问题,心情很开朗,但愿各位小伙伴也能遇到这种开明的领导。(确定不是阿粉的颜值高)
在此次问题排查过程当中,熟悉了问题排查步骤,巩固了 jdk 工具的使用方法和流程,也加深了对业务的理解程度,果真 「遇到问题可以快速成长」
同时解决 BUG 后,阿粉的心里更开心了,下班后点了一份小碗鸭血粉丝。
各位小伙伴看完以为有趣或者有用,来个点赞和关注,让阿粉能升级吃大碗哈哈哈~