记录一次线上问题的排查过程

概述

作为程序员,大家肯定少不了排查线上问题的经历。往往一个线上问题会导致多种现象,多种现象往往意味着有多重排查的思路和方向。如果排查方向没有找对,往往结果是不仅耽误了故障恢复时间而且使排查走到了死胡同。找对方向往往是最重要的,但是说起来简单,真实的故障定位往往依靠经验和对系统的理解,没有一个统一的流程。本篇文章记录下自己排查问题的过程,分享下经验。

现象

那是一个风和日丽的周末下午,手机突然收到0571开头的电话。刚开始还以为又是房产销售或者保险销售的电话,不耐烦的接起了电话。但是听到了熟悉的“集团监控报警...”后,整个人一下子清醒了。一个不轻松的周末下午又要开始了。挂掉电话后看了下报警详情,是我们某个应用的某个接口有5%的错误率。我们的接口是通过pop平台对外暴露的服务,pop平台上显示这个接口有5%的请求响应为503。

过程

怀疑依赖的A服务超时

看到这里我首先想到的问题是,是否我们业务强依赖的某个接口出了问题导致我们的服务出现异常。找个几个错误的请求,发现503的请求耗时都很高,都超过了3s,而这个接口超时时间是3s,超过了3s的请求在pop平台上都会归类为503。所以进一步的猜测是我们强依赖的某个服务超时,导致我的服务超时。之所以这么想是因为,以前经常遇到这样的情况。通过arms链路诊断工具查看耗时高的请求发现依赖的A服务耗时非常高达到了6s之多。查到了这里可能是A服务出了问题,想着这下终于找到了问题。进一步查看A服务监控发现,A服务平均RT非常稳,是正常的RT范围。这是还不死心,觉得会不是平均RT没问题,但是存在长尾请求导致部分请求超时呢?再进一步查看了P99监控,发现RT依然没有问题。这时候想着锅甩不出去了,开始没了思路。回头一想,A服务居然有响应时间为6s的情况。不对啊,A服务的超时时间被设置了2s,客户端不可能有执行6s的情况。这里是个伏笔,指向了最终问题所在。

多个服务均出现超时现象

继续通过Arms查看超时的请求,发现不仅是A服务有超时的问题,应用依赖的B服务,C服务,D服务都出现了超时的case。但是查看相应应用对应的监控,均未发现问题,RT都是正常的水平。查到这里,基本可以否定我们强依赖的A服务出现问题导致耗时高。因为如果是A服务的问题的话,超时的case应该更收敛,都聚焦在A耗时高上。但是超时问题很发散,很多依赖的服务都出现超时的问题。这个“百花齐放”的现象,让问题排查的方向从外部开始转向内部。是不是就是我的应用本身的问题?

FullGC

虽然很不愿意承认,但是直觉告诉我,大概率是自己的应用出了问题。在我正一筹莫展的时候,同事发现了JVM的问题。通过JVM的监控可以看出,应用一直在FullGC!这么明显的特征,居然没看到。所以,这里可以看出来方向真的很重要,自己前面的排查是在错误的方向上走了几步,做了无用功,没有找到根本问题。既然知道了是FullGC问题就好办了,哪个Java程序员没背过八股文。当出现了FullGC问题,首先这样,然后那样,再那样...每个人都很清楚。问题是线上问题不会直接告诉你,我是因为FullGC导致的,它往往伴随着多种现象,容易让人找错方向。

处理流程

处理FullGC的流程,是很标准的流水线作业。

  1. 找到问题机器,下线在线服务。

    1. 下线在线服务是为了dump堆作准备。dump期间这台机器在线服务基本不可用,如果不下线可能会导致更多的服务失败。

  2. dump出来堆内存。

    1. dump堆是为了后面分析,找到根本原因

  3. 将应用重启。

    1. 重启是快速恢复的一种手段,可以将堆内存的使用率降下来,临时解决GC的问题。

  4. 分析堆内存,找出无法被回收的对象。

    1. 找到根本原因,然后针对性的修复。

根本原因

接下来就是分析导出的堆文件,找到问题的元凶。

从这张图片可以看出,这个org.mockito.internal.stubbing.InvocationContainerImpl @ 0x749845090对象就是问题的元凶。一个对象占用了60%的内存空间肯定有问题。这个对象是Mockito框架中的对象。

这个组件的职责是记录mock调用的上下文信息,上下文信息包括方法的入参、返回值等非常多的数据。它存在两种实现,第一种实现会记录所有的历史调用记录,第二种实现只会记录最后一次调用的记录,也叫做存根。

默认是使用第一种实现,因为每次调用的上下文都会被记录下来存在了一个LinkedList中,不存在淘汰机制,所以呢随着mock对象对mock方法调用次数的增加,上下文信息逐渐增加,最终导致占用大量内存,结果就是JVM无法回收这部分内存导致频繁FullGC。解决办法就是在mock对象时,设置只保留最后一次调用的上下文信息。

总结

这里还有一个问题,就是Mockito是被设计用在测试代码中的,不应该被用在生产环境。当生成环境需要Mock一些对象时候,不应该使用Mockito框架。可选的办法是手动创建代理对象,这样可以避免踩到未知的坑。另外,FullGC问题往往有一定的隐蔽性,一开始潜伏在应用中,逐步积累,超出临界值时,再一下子暴露出来。本次问题排查,体会最深的感受是,问题排查的方向真的很重要。这种方向感需要靠经验和实践的积累而形成,不仅仅是有理论知识就可以了。常言道,纸上得来终觉浅,绝知此事要躬行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值