线上问题排查方法论

线上问题是初级开发人员接触不到的,但是又必须去看的问题。

刚毕业的这一年,真的是什么都有幸接触到了。去机房修过服务器、抢救过服务、线上问题诊断、复盘、JVM调优、再到架构、最终回到底层。

可谓是千锤万凿出深山,烈火焚烧莫等闲!真的经历这么一遭,反而明白了一个道理,世界的终点是起点。为什么这么说呢?经历过线上事故,进行深度复盘以后,才明白,原来扎实的基础知识,例如多线程,JVM,操作系统原理这些,是这么的重要。经历过大风大浪还有幸存活的渔夫,一定会把它的渔船修的异常坚固,一定会准备必要的逃生方案。

哎呀跑题了,这篇文章是要讲线上问题排查思路的!言归正传。

文章属于Java体系,阅读要稍微了解:JVM

目录

 

经验之谈:凡事都有事前-事中-事后

经验之谈:九一法则

事前-预防

扎实的基础

合理的架构

完整的监控体系

上线前的压测

应该做好问题出现的准备

给自己留点后路

事中-如何定位问题

发现问题

排查问题的思路,要清晰

操作系统本身的问题

程序本身的bug

CPU负载过高-无BUG但是JVM需要调优

CPU负载过高-有BUG

事后-复盘问题

关于arthas


经验之谈:凡事都有事前-事中-事后

  事前预防;事中冷静,出问题不能慌,清晰的思路最重要,不过慌不慌是建立在有没有深厚的知识储备上地;事后复盘,找出问题所在,以及得出结论,如何避免。 

  正所谓:世界的重点是起点。经验是你更有把握起做成一件事的资本,更是我们程序员的价值所在。

 

经验之谈:九一法则

 百分之九十以上问题出于应用程序本身;百分之十不到的问题有可能是环境因素:操作系统内核、磁盘、网络、内存、JVM参数

 百分之九十的努力应该放在事前,百分之十放在事中事后。


事前-预防

空即色,色即空。最好解决bug 的手段是不写bug。

扎实的基础

  我们需要扎实的基础,才能支撑我们走的更远,写出更加优秀的代码。多线程、设计模式以及设计原则、操作系统原理甚至是linux内核。有太多的原理我们要去攻克。成功了越走越远,拿钱拿股票;没成功,拿辞职信就行了。百分之九十的问题,是因为底层原理不扎实,想当然的去编码导致的。

合理的架构

  所谓合理的结构都是经验的积攒,没人能够在问题出现之前,就想到所有 的问题,只能靠经验,来避免百分之八十的问题。

完整的监控体系

  例如机器方面的监控:CPU负载、内存占用率、网络情况、磁盘IO情况。我们使用的是zabix来做机器方面的监控。通常机器方面的监控是最早发现问题的前兆。应用系统方面的监控,特别是在长链路的分布式系统中,任何一个问题出现问题,都有可能蔓延到其它系统,造成雪崩的事故。这包括了一个慢查询,造成的某个环节的积压。所以不要让你的应用系统裸奔!这里我推荐一下美团开源的CAT监控系统。CAT能实时监控各个指标,各个链路事件。包括服务器的CPU负载,JVM内存,GC信息,线程信息,慢URL,慢SQL,请求响应耗时平均线、95线、99线,以及配置单位时间内多少服务error告警等

        

上线前的压测

  至少持续半个小时以上的高负载的压测,甚至有必要去做24小时的压测,有几年经验的朋友,应该会遇到一些问题,就是有些问题简单测试都不能复现。项目刚启动起来,没有问题。运行一段时间就有问题了。通过重启可以暂时的缓解问题。但是还是会出现。有些问题只有在持续的高负载下才会出现,有些问题只有在持续比较长的时间才会出现,例如不太明显的内存泄露。

应该做好问题出现的准备

  从架构的角度出发,应该有预案。挂掉以后应该怎么办,如果不能完全避免,如何最大程度的降低损失;

  从实现的角度出发,这就要求开发人员面向错误编程,面向事故编程。对于初级开发人员来说,它的程序永远都是面向完全正常的逻辑去编码地。这就是功能实现了,但是bug一大堆的最大原因。就一个最简单的栗子:你写了一个方法,方法的入参是一个对象,然后你在方法里边有去操作这个对象,比方说调用入参对象的get方法,而没有去想有可能传进来的是空对象,所以就空指针异常了。有人说这个方法是我自己写自己调用的,我自己知道不会传空对象。我问一下:你敢拿头保证不传空吗?如果不敢,就面向错误编程,编程的世界没有那么美好,甚至能想多糟糕就想多糟糕。

给自己留点后路

  所以要合理的打印日志。要明白一个事实,日志不是打印给组长看的(当然出现bug的时候,就是组长看的了),日志其中一个最大的作用就是排查问题,定位问题。我们可以根据日志来排除问题,我们能够知道问题究竟出在哪个环节。前边提到过,有这样的bug,不能一下子就复现,一会儿有,一会儿没有,甚至很长时间才能出现一次。所以不要给自己挖坑,把日志写明白。最好能做到,看一眼日志,就知道你这个代码有没有问题。


事中-如何定位问题

 如果按照我上边提到的事前预防来做,你到这一步其实应该只有百分之十的概率。我把这个称为是九一分布法则。出了问题以后,第一个要想的是如何解决,如何快速修复,而不是追责,就算真的算账也要等到秋后!接下来看一下如何快速的排查问题,少走弯路。

发现问题

 上边提到过,像CPU负载,内存占有率,是非常容易反映问题的。其中CPU长时间负载过高,是百分之九十以上的问题,而这个问题的产生,多半是由于编码错误导致的。如果有合理的监控机制,你应该是很快能察觉到问题的。如果没有完整的监控体系。想要发现问题,通常是问题已经很严重了,例如系统出现很严重的卡顿现象。此时已经给用户带来极差的体验了,很容易造成用户流失。假如支付宝出现一个小时打不开的情况,作为用户,就问题慌不慌。第一次出现你慌了,我存的钱怎么办。第二次,第三次呢?你是不是会把钱取出来放在别的平台,然后卸载应用。这就是用户流失,用户是有容忍度的。

排查问题的思路,要清晰

  我比较推荐的是排除法。有一些问题出现了的时候,可以通过日志快速的发现问题(日志打印我在事前的环节已经讲了),不过还是有一些问题不能通过日志发现,或者说日志看不出来什么问题。但是问题又很灵异。这种问题一共有这么几个原因(目前我遇到的),操作系统的内核bug,这个之前遇到过,当时是在12306,服务器出现周期性的宕机,有时候是一个月,有时候是一个半月,周期非常的长,想要完全复现是需要时间的。这个最终是通过红帽的系统工程师来解决的,得出的结论是系统内核bug。这是最搞人的,不过是出现几率最小的。

操作系统本身的问题

 因为操作系统本身的问题而产生的问题,这会给我们带来很大的困扰,这会极大的浪费我们的时间,去排查一个本来没有问题的代码逻辑,然后怎么 都不能得到答案。这种操作系统出问题的概率不大,不过我们还是要先排除掉。我们要关注一下内核对CPU的消耗。我建议看看这篇文章,看看如何排除系统CPU,磁盘IO,以及网络的问题。https://zhuanlan.zhihu.com/p/336742950

程序本身的bug

  应该说百分之九十五以上的问题,都是程序本身的bug,而不是操作系统带来地。这也是很多人排查问题的时候先去排查代码原因。上边也提到过,百分是九十以上的问题是编码问题造成的bug。例如死锁、内存泄露、我不准备在这篇文章去科普死锁是怎么产生的,如何避免死锁,这也是我上边提到的基础知识。我推荐一本书《java深度调试技术》。这本书会让我们去合理的避免写出bug。其实最怕的是比不知道这样写会是bug。死锁、内存泄露是会造成CPU飙升的两个最常见的情况,死锁会出现不释放CPU的现象,而内存泄露会造成频繁的垃圾回收,其中GC线程占用了多数的CPU。举个栗子,加入你给JVM堆内存分配的两个G,但是因为你没有释放对象,例如没有释放连接,没有关闭流等等原因,永久性的占用着堆内存,导致JVM 的GC线程一直去尝试清理垃圾,但是一直不能把垃圾清理掉。文章有限,非常建议去看看《java深度调试技术》这本书。如果对JVM陌生,至少应该读一本JVM方面的书《实战JVM虚拟机》,或者《深入理解JVM虚拟机》

CPU负载过高-无BUG但是JVM需要调优

  如果确定是CPU负载过高,先去确定是代码的bug,还是只是需要JVM调优而已。

  解决问题永远要先使用最简单最有效的手段。例如linux的top命令,就能看出来是不是java进程占用的CPU过高。举一个反面栗子,根本不是CPU占有率过高,你硬是起排查JVM的问题,这岂不是南辕北辙了!如果说使用TOP命令看出来是你的应用服务进程导致的CPU负载过高,先确定一下,是不是持续过高,偶然的过高是正常的,本省FullGC是会花费较多的资源的。

  最后确定是应用服务长时间CPU负载过高,那就应该去确定一下JVM的情况了。个人比较青睐于阿里的开源工具 arthas(下边会稍微介绍一下),操作起来非常的方便,就一个jar包,运行起来,简单的几个命令就可以去观察到你的JVM使用情况。具体的去看一下线程的情况,一眼就能看出来是不是GC线程在占用着CPU。如下图,就一个dashbord的命令,就能看出来上边使用top线程的情况。下边就是JVM的情况,年轻代垃圾回收的次数,以及累计时间,FullGC的次数,以及时间。注意观察FullGC的次数,如果在很短时间内,就进行了FullGC,这是有问题的,要么是代码的问题,造成了内存泄露;要么是JVM需要调优。

  这篇文章不展开太多了,只举一个栗子,比方说因为程序的特殊性,而年轻代又比较小,YongGC频繁了,一些对象很容易就要到老年代了,而老年代满了就要FullGC,花费时间又巨多。程序出现了卡顿现象,也并不是代码原因,就是单纯的JVM不合理。稍微进行调优,适当的增加年轻代的大小,合理的调一下service区和edan区的比例,就发现一切都好了。如果通过调优依旧不能解决,那就要定位代码的问题了。

      

CPU负载过高-有BUG

  确定是代码问题,如何去定位代码的bug呢?第一种是内存泄露问题:这个方法是比较多的,条条大路通罗马。能定位到代码bug在哪里的方法就是好猫。我这里说一个我的方法,首先我这边的使用的工具是Jprofile,一款收费的软件,真的好用,想要白嫖,网上有绿色版的。如果服务器不能联网的话:先在服务器上dump一个堆栈快照下来,然后只能拷贝出来,然后再使用Jprofile分析了。这里我优先关注的是大对象,数量最多的对象,此时是这些对象占用着老年代的空间,然后我打开了它的GCroot,然后Jprofile也是可以继续展开看到具体的执行代码的,一个环节一个环节的去分析代码是否有问题。还是重点关注可疑对象。例如流关闭,全局变量,常量,举个栗子:你有一个常量,你在程序里边有去向里边放对象,但是却忘了在使用后移除。这就造成了内存泄露。另外比如说创建的线程,但是一直没有启动,这也是不能回收的垃圾。另外在《java深度调试技术》这本书中,是使用JProfile直连服务器的。思路是这样的,先让服务处于一个压力状态,然后使用JProfile进行一次对象标记,然后再手动触发垃圾回收,多触发几次,最后依旧留下来的对象,就是重点可疑对象,这些极有可能是造成内存泄露的原因。

  如果是死锁的话:使用arthas的 thread命令可以看到有没有死锁。如果有的话具体的定位到线程,然后再根据死锁的产生原因去分析:出现锁的互相等待,或者循环等待,这个根据具体的场景去分析。

     


 

事后-复盘问题

  对于第二个步骤分析出来的问题,肯定已经知道问题是怎么产生的了,然后记住这个经验教训,形成好的代码规范。例如阿里巴巴的《java开发手册》,其实也是这么来的。事后复盘,更能积累经验,去做好第一步,事前预防。

 

关于arthas

使用好的工具来发现这些体征 arthas(问题问题的工具)

  这里我选择的是阿里开源的 arthas,这个工具。不做过多的介绍了,就是要用,最主要的是容易上手,能够帮助我们发现问题。具体的使用步骤,我不赘述,贴一个官网的教程:https://arthas.aliyun.com/doc/install-detail.html

  这个我之前只是了解过,没有真的使用过,我在解决问题的时候,就是看着官网的教程,顺利的使用起来的,真的是超级简单,所以我推荐的就是这个工具。

 

如何使用 arthas 来进一步的发现问题

https://arthas.aliyun.com/doc/quick-start.html

  上边的链接,就是启动 arthas的官网,照着做下来就可以了。使用 dashbord 命令,我截图的部分就是结果,其中红色框就是 年轻代GC 和 老年代 GC的 次数,以及花费的时间。其中第一行是年轻代GC次数,第二行是花费时间,第三行是老年代GC的次数,第四行,是花费是时间。 这个截图就是生产环境下的真实情况。这是刚启动以后的,可以发现,这个GC的次数老年代已经这么多次了,随着不停的刷新,发现每秒老年代的GC 都发生了好多次。到这里为止,已经可以看出来,垃圾回收的情况,如果过于频繁,就说明有调优的空间。其中老年代的GC 几个小时一次,甚至做的足够好,几天一次。

 

 

 https://arthas.aliyun.com/doc/jvm.html

 接下来,排查是不是因为发生了死锁,是不是操作不当,创建了很多的线程。操可以看上边的文章。就是使用jvm 命令,然后检查死锁。arthas 好在可以直接给我们进行一个数据,告诉我们是否有死锁发生。理论上来讲,不会创建太多的线程。

 

 

 

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值