2.3 根本原因
通过日志分析以及 dump 文件分析,都指向了某个文件导出接口,接着在代码中分析该接口具体调用链路,发现导出的数据很多,而且老代码进行计算的逻辑嵌套了很多 for 循环,而且是 for 循环调用数据库,计算效率极低。
模拟了该用户在这个接口的所调用数据量,需要查询多个表,然后 for 循环中大概会计算个 100w+ 次,导致阻塞了其它请求,由于请求未结束,java 对象无法被 GC 回收,线上的服务器 CPU 和内存使用情况一直飙升。
3 性能优化
3.1 业务、代码逻辑梳理
点开该段代码的 git 提交记录,发现是我在实习期写的时候,我的内心是崩溃的,当时对业务不熟悉,直接循环调用了老代码,而且也没有测试过这么大的数据量,所以 GG了。
然后我就开始做代码性能优化,首先仔细梳理了一下整个业务流程,通过增加 SQL 查询条件,减少数据库 IO 和查询返回的数据量,优化判断条件,减少 for 嵌套、循环次数和计算量。
3.2 通过 VisualVM 进行对比
官方描述:
VisualVM
,能够监控线程,内存情况,查看方法的CPU时间和内存中的对象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的).
该插件不需要另外下载,在 ${JAVA_HOME}/bin
目录下就能找到,所以安装了 jdk 就能使用它~
对比了新老代码所占用的 CPU 时间和内存状态
优化前:
优化后:
通过上述优化之后,计算 1w 条数据量,进行导出成报表文件,在老代码需要 48s,新代码下降到了 8s,不过这是大数据量的情况下,实际用户的数据没有这么多,所以基本上满足了线上 99% 的用户使用。(看到这个结果时,阿粉露出了「纯洁的微笑」)
当然,由于这些数据是本地开发环境新增加的,与出现 OOM 问题的用户数据量还有些差别,但通过优化后的代码,已经在数据库查询的时候就过滤掉很多无效的数据,在 for 循环计算前也加了过滤条件,所以真正计算起来起来就降低了很多计算量!
恩,自己代码优化好了,还要等测试爸爸们测试后才敢上线,这次要疯狂造数据!
4 技术总结
阿粉周末会自己做点饭🍚,喜欢看王刚老师的视频,觉得最后出现的 「技术总结」 很棒,让我能够快速记住重要步骤
(但学习跟做饭一样,还是得经过反复看和实践才能好好记住,哼儿哈儿,大伙知道阿粉想说的是啥了吧哈哈哈)
4.1 开发注意点
在开发初期,阿粉没有考虑到性能问题,想着满足需求就完成任务,但数据量一大起来,就有可能出现这些 OOM 问题,所以以后开发时,需要先提前考虑以下几点:
- 梳理设计流程
- 考虑是否有性能问题
- 与产品经理商量控制查询条件,减少查询的范围
- 与数据库交互时,减少无效的查询,合并查询和合并更新操作
- 减少 for 循环,尤其注意多层 for 循环嵌套问题!
- 调用老代码要注意=-=
4.2 应对故障注意点
出现问题时也不要惊慌,好好去解决它才是王道,「在解决问题中学到更多技术」
可以参考以下步骤:
- 保留现场,dump 堆栈信息
- 进行限流,对出现问题的接口进行降级
- 如没有降级措施,服务器本身无法快速恢复正常,联系运维重启进行恢复的同时,记录下有可能出现问题的数据,等待后面修复
- 日志、堆栈信息分析
- 定位问题并解决
5 多说两句
在定位到问题时,看到是阿粉写的,原本以为会受到批评,但领导并没有责怪我,还叫阿粉之后好好改,避免下次出现这种问题,心情很开朗,希望各位小伙伴也能遇到这种开明的领导。(肯定不是阿粉的颜值高)
在这次问题排查过程中,熟悉了问题排查步骤,巩固了 jdk 工具的使用方法和流程,也加深了对业务的理解程度,果然 「遇到问题能够快速成长」
同时解决 BUG 后,阿粉的内心更开心了,下班后点了一份小碗鸭血粉丝。
各位小伙伴看完觉得有趣或者有用,来个素质三连,帮忙点个在看,让阿粉能升级吃大碗哈哈哈~
精彩回顾: