记一次SpringBoot服务生产环境内存占用过高的排查

那年烟雨落申城
• 阅读 470

缘起

生产环境服务A部署在K8s上,某天运维告诉我这个服务经常会重启,客户没有报告是因为我们是滚动发布更新,先启动这个服务的一个新实例,然后将旧实例Kill掉,这样前端是无感知的,但重启是实实在在存在的,生产问题不可马虎,于是开启了定位问题之旅。

过程

定位问题前前后后一共花了快一个月,过程如下:

  1. 服务是Java写成的,监控有Prometheus和ARMS(阿里商业监控),Prometheus只能看到CPU和内存用量,我也看了Prometheus监控,内存大致如下: 记一次SpringBoot服务生产环境内存占用过高的排查 CPU大致如下: 记一次SpringBoot服务生产环境内存占用过高的排查 还有GC等指标,其实不用看了,大概就是老年代满了,没内存了,但是日志里没有OOM错误,这是为什么?原因是部署在K8s,在OOM之前 health check的响应时长过长,K8s认为服务挂了,就启动了个新实例,将旧实例Kill掉。
  2. 看此段时间内的请求,某个接口请求比较频繁,于是猜测这个接口频繁调用会导致服务重启,但猜测毕竟是猜测,要去验证的。于是在UAT环境进行验证,但是由于UAT数据量级和PRD量级差距过大,只是发现UAT内存增加了,并未出现和生产环境一样的现象——healthcheck导致服务重启。又去请求DBA同学导一下生产数据往UAT库,但被Security团队拒绝了。这下没法在UAT测试和验证了,于是主意又打到PRD环境,和领导聊过之后,终于在某个周五晚上(因为是内部系统,周五客户下班后,这个系统基本没人使用),疯狂调用上面发现的接口,将服务搞挂了,监控和上面一致,复现了这个问题,确定是这个接口导致的。
  3. 确定了是这个接口导致的,只能知道把内存打满了,但不知道因为内部逻辑的哪一块导致的,从代码角度看的话不容易看出来,而且看出来也没有直接的证据证明确实是这段代码,没法给领导汇报,于是考虑将出现这个问题时的内存dump一下。首先考虑到的是使用aliyun的ARMS进行dump,我让同事请求这个接口,然后我就看着监控,内存上去后就手动点Dump按钮,采集了几次打开感觉都不对,内存没有任何异常,而且吐槽一下,ARMS的Dump这个功能,一分钟之内只能点一次,实际把握不好,真的采集不到合适的数据。试了几次后就放弃了,于是考虑新的方案。
  4. 新的方案就是我们在K8s容器将要被Kill的时候执行jmap,jstack等命令,实施前才想到,我们用的基础镜像是包含jre的,不包含jdk,根本不支持jmapjstack等命令,然后找运维去帮忙换成JDK的基础镜像,被拒绝了,只能自立更生了,在网上看到了Jattch这个东西,于是在SIT环境测试了下,居然真可以用,详情可参见 我的另一篇文章Docker容器只有JRE没有JDK使用Jattach导出内存快照 最终使用到的脚本是
    #!/bin/sh
    # 导出当前内存信息
    jattach 1 dumpheap /opt/dump/dumpheap_"$HOSTNAME"_`date +%Y%m%d-%H%M%S`.hprof
    # 导出当前线程信息
    for i in `seq 3`
    do 
     jattach 1 threaddump > /opt/dump/threaddump_"$HOSTNAME"_`date +%Y%m%d-%H%M%S`.log && sleep 1
    done
    # 导出当前使用CPU最高的线程
    top -H -p 1 -n 3 -c -b > /opt/dump/cpudump_"$HOSTNAME"_`date +%Y%m%d-%H%M%S`.log
  5. 导出文件的目录让运维挂载到了一个网络硬盘上,然后拿下来就可以分析了,期间一共导下来4个dump文件,其中3个事损坏的,个人猜测应该是没有导出完成,容器就被Kill了,我们优雅退出的等待时间是30秒

    结果

    那么我们来分析下唯一可以打开的这个文件,使用MAT(MemoryAnalyzer Tool) 载入后如下: 记一次SpringBoot服务生产环境内存占用过高的排查

可以明显看出有个线程池的某个线程居然内存占用达到2.5G,结合上面Prometheus的老年代一共2.6G可以得知是这个线程把内存吃满了,到底是哪个呢?点击Reports下的Leak Suspects,可以看到: 记一次SpringBoot服务生产环境内存占用过高的排查

看到这里就比较清楚了,下面有方法的调用栈,如果看详细的,可以点See stacktrace查看详细调用栈。 记一次SpringBoot服务生产环境内存占用过高的排查 至此,问题定位到,剩下的就是去看逻辑,优化代码了。

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
RAC环境单实例启动数据库收到ORA
     在RAC环境中,如果你在没有启动节点的集群服务的情况下单实例启动数据库,将收到类似如下的报错:\oracle@rhel1u01\$sqlSQL\Plus:Release10.2.0.5.0ProductiononTueApr215:00:272013Copyright(
Stella981 Stella981
3年前
Python+Selenium自动化篇
本篇文字主要学习selenium定位页面元素的集中方法,以百度首页为例子。0.元素定位方法主要有:id定位:find\_element\_by\_id('')name定位:find\_element\_by\_name('')class定位:find\_element\_by\_class\_name(''
Stella981 Stella981
3年前
Eureka Server集群重启问题追踪
问题在生产环境重启EurekaServer集群的时候,发现订单客户端调用分布式Id生成服务出错,1Caused by: com.netflix.client.ClientException: Load balancer does not have available server for client: IDG显示订单服务调不到
Stella981 Stella981
3年前
Kerberos无约束委派的攻击和防御
 0x00前言简介当ActiveDirectory首次与Windows2000Server一起发布时,Microsoft就提供了一种简单的机制来支持用户通过Kerberos对Web服务器进行身份验证并需要授权用户更新后端数据库服务器上的记录的方案。这通常被称为Kerberosdoublehopissue(双跃点问题),
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
K8S为何杀死我的应用
首发公众号:二进制社区,转载联系:binary0101@126.com导读"K8S为我们提供自动部署调度应用的能力,并通过健康检查接口自动重启失败的应用,确保服务的可用性,但这种自动运维在某些特殊情况下会造成我们的应用陷入持续的调度过程导致业务受损,本文就生产线上一个核心的平台应用被K8S频繁重启调度问题展开剖解,抽丝剥茧一
Stella981 Stella981
3年前
Node 中异常收集与监控
在一个后端服务设计中,异常捕获是必不可少需要考虑的因素而当异常发生时,能够第一时间捕捉到并且能够获得足够的信息定位到问题至关重要 这也是本篇文章的内容刚开始,先抛出两个问题1.在生产环境中后端连接的数据库挂了,是否能够第一时间收到通知并定位到问题,而不是等到用户反馈之后又用了半天时间才找到问题(虽然运维肯定会在第一时间知道数据库挂了)2
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Docker容器只有JRE没有JDK使用Jattach导出内存快照
缘起目前生产环境(k8s部署的)发现某个服务被重启了,当前监控只有普罗米修斯,可以看到当时的内存和CPU都很高。服务接入了阿里的监控工具ARMS,但是没法导出当时的内存快照,ARMS虽然提供了手动导出的功能,但是人很难确定哪个时间点的内存快照是最合适的。虽