你好,我是朱晔。
咱们这个课程已经更新13讲了,感谢各位同学一直在坚持学习,并在评论区留下了很多高质量的留言。这些留言,有的是分享自己曾经踩的坑,有的是对课后思考题的详细解答,还有的是提出了非常好的问题,进一步丰富了这个课程的内容。
有同学说,这个课程的案例非常实用,都是工作中会遇到的。正如我在开篇词中所说,这个课程涉及的100个案例、约130个小坑,有40%来自于我经历过或者是见过的200多个线上生产事故,剩下的60%来自于我开发业务项目,以及日常审核别人的代码发现的问题。确实,我在整理这些案例上花费了很多精力,也特别感谢各位同学的认可,更希望你们能继续坚持学习,继续在评论区和我交流。
也有同学反馈,排查问题的思路很重要,希望自己遇到问题时,也能够从容、高效地定位到根因。因此,今天这一讲,我就与你说说我在应急排错方面积累的心得。这都是我多年担任技术负责人和架构师自己总结出来的,希望对你有所帮助。当然了,也期待你能留言与我说说,自己平时的排错套路。
要说排查问题的思路,我们首先得明白是在什么环境排错。
接下来,我就与你详细说说,如何在生产环境排查问题吧。
其实,排查问题就像在破案,生产环境出现问题时,因为要尽快恢复应用,就不可能保留完整现场用于排查和测试。因此,是否有充足的信息可以了解过去、还原现场就成了破案的关键。这里说的信息,主要就是日志、监控和快照。
日志就不用多说了,主要注意两点:
对于监控,在生产环境排查问题时,首先就需要开发和运维团队做好充足的监控,而且是多个层次的监控。
我们再来看看快照。这里的“快照”是指,应用进程在某一时刻的快照。通常情况下,我们会为生产环境的Java应用设置-XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath=…这2个JVM参数,用于在出现OOM时保留堆快照。这个课程中,我们也多次使用MAT工具来分析堆快照。
了解过去、还原现场后,接下来我们就看看定位问题的套路。
定位问题,首先要定位问题出在哪个层次上。比如,是Java应用程序自身的问题还是外部因素导致的问题。我们可以先查看程序是否有异常,异常信息一般比较具体,可以马上定位到大概的问题方向;如果是一些资源消耗型的问题可能不会有异常,我们可以通过指标监控配合显性问题点来定位。
一般情况下,程序的问题来自以下三个方面。
第一,程序发布后的Bug,回滚后可以立即解决。这类问题的排查,可以回滚后再慢慢分析版本差异。
第二,外部因素,比如主机、中间件或数据库的问题。这类问题的排查方式,按照主机层面的问题、中间件或存储(统称组件)的问题分为两类。
主机层面的问题,可以使用工具排查:
组件的问题,可以从以下几个方面排查:
第三,因为系统资源不够造成系统假死的问题,通常需要先通过重启和扩容解决问题,之后再进行分析,不过最好能留一个节点作为现场。系统资源不够,一般体现在CPU使用高、内存泄漏或OOM的问题、IO问题、网络相关问题这四个方面。
对于CPU使用高的问题,如果现场还在,具体的分析流程是:
如果没有条件直接在服务器上运行top命令的话,我们可以用采样的方式定位问题:间隔固定秒数(比如10秒)运行一次jstack命令,采样几次后,对比采样得出哪些线程始终处于运行状态,分析出问题的线程。
如果现场没有了,我们可以通过排除法来分析。CPU使用高,一般是由下面的因素引起的:
对于内存泄露或OOM的问题,最简单的分析方式,就是堆转储后使用MAT分析。堆转储,包含了堆现场全貌和线程栈信息,一般观察支配树图、直方图就可以马上看到占用大量内存的对象,可以快速定位到内存相关问题。这一点我们会在第5篇加餐中详细介绍。
需要注意的是,Java进程对内存的使用不仅仅是堆区,还包括线程使用的内存(线程个数*每一个线程的线程栈)和元数据区。每一个内存区都可能产生OOM,可以结合监控观察线程数、已加载类数量等指标分析。另外,我们需要注意看一下,JVM参数的设置是否有明显不合理的地方,限制了资源使用。
IO相关的问题,除非是代码问题引起的资源不释放等问题,否则通常都不是由Java进程内部因素引发的。
网络相关的问题,一般也是由外部因素引起的。对于连通性问题,结合异常信息通常比较容易定位;对于性能或瞬断问题,可以先尝试使用ping等工具简单判断,如果不行再使用tcpdump或Wireshark来分析。
有些时候,我们分析和定位问题时,会陷入误区或是找不到方向。遇到这种情况,你可以借鉴下我的九个心得。
**第一,考虑“鸡”和“蛋”的问题。**比如,发现业务逻辑执行很慢且线程数增多的情况时,我们需要考虑两种可能性:
出现问题的时候,我们需要结合内部表现和入口流量一起看,确认这里的“慢”到底是根因还是结果。
**第二,考虑通过分类寻找规律。**在定位问题没有头绪的时候,我们可以尝试总结规律。
比如,我们有10台应用服务器做负载均衡,出问题时可以通过日志分析是否是均匀分布的,还是问题都出现在1台机器。又比如,应用日志一般会记录线程名称,出问题时我们可以分析日志是否集中在某一类线程上。再比如,如果发现应用开启了大量TCP连接,通过netstat我们可以分析出主要集中连接到哪个服务。
如果能总结出规律,很可能就找到了突破点。
**第三,分析问题需要根据调用拓扑来,不能想当然。**比如,我们看到Nginx返回502错误,一般可以认为是下游服务的问题导致网关无法完成请求转发。对于下游服务,不能想当然就认为是我们的Java程序,比如在拓扑上可能Nginx代理的是Kubernetes的Traefik Ingress,链路是Nginx->Traefik->应用,如果一味排查Java程序的健康情况,那么始终不会找到根因。
又比如,我们虽然使用了Spring Cloud Feign来进行服务调用,出现连接超时也不一定就是服务端的问题,有可能是客户端通过URL来调用服务端,并不是通过Eureka的服务发现实现的客户端负载均衡。换句话说,客户端连接的是Nginx代理而不是直接连接应用,客户端连接服务出现的超时,其实是Nginx代理宕机所致。
**第四,考虑资源限制类问题。**观察各种曲线指标,如果发现曲线慢慢上升然后稳定在一个水平线上,那么一般就是资源达到了限制或瓶颈。
比如,在观察网络带宽曲线的时候,如果发现带宽上升到120MB左右不动了,那么很可能就是打满了1GB的网卡或传输带宽。又比如,观察到数据库活跃连接数上升到10个就不动了,那么很可能是连接池打满了。观察监控一旦看到任何这样的曲线,都要引起重视。
**第五,考虑资源相互影响。**CPU、内存、IO和网络,这四类资源就像人的五脏六腑,是相辅相成的,一个资源出现了明显的瓶颈,很可能会引起其他资源的连锁反应。
比如,内存泄露后对象无法回收会造成大量Full GC,此时CPU会大量消耗在GC上从而引起CPU使用增加。又比如,我们经常会把数据缓存在内存队列中进行异步IO处理,网络或磁盘出现问题时,就很可能会引起内存的暴涨。因此,出问题的时候,我们要考虑到这一点,以避免误判。
**第六,排查网络问题要考虑三个方面,到底是客户端问题,还是服务端问题,还是传输问题。**比如,出现数据库访问慢的现象,可能是客户端的原因,连接池不够导致连接获取慢、GC停顿、CPU占满等;也可能是传输环节的问题,包括光纤、防火墙、路由表设置等问题;也可能是真正的服务端问题,需要逐一排查来进行区分。
服务端慢一般可以看到MySQL出慢日志,传输慢一般可以通过ping来简单定位,排除了这两个可能,并且仅仅是部分客户端出现访问慢的情况,就需要怀疑是客户端本身的问题。对于第三方系统、服务或存储访问出现慢的情况,不能完全假设是服务端的问题。
**第七,快照类工具和趋势类工具需要结合使用。**比如,jstat、top、各种监控曲线是趋势类工具,可以让我们观察各个指标的变化情况,定位大概的问题点;而jstack和分析堆快照的MAT是快照类工具,用于详细分析某一时刻应用程序某一个点的细节。
一般情况下,我们会先使用趋势类工具来总结规律,再使用快照类工具来分析问题。如果反过来可能就会误判,因为快照类工具反映的只是一个瞬间程序的情况,不能仅仅通过分析单一快照得出结论,如果缺少趋势类工具的帮助,那至少也要提取多个快照来对比。
**第八,不要轻易怀疑监控。**我曾看过一个空难事故的分析,飞行员在空中发现仪表显示飞机所有油箱都处于缺油的状态,他第一时间的怀疑是油表出现故障了,始终不愿意相信是真的缺油,结果飞行不久后引擎就断油熄火了。同样地,在应用出现问题时,我们会查看各种监控系统,但有些时候我们宁愿相信自己的经验,也不相信监控图表的显示。这可能会导致我们完全朝着错误的方向来排查问题。
如果你真的怀疑是监控系统有问题,可以看一下这套监控系统对于不出问题的应用显示是否正常,如果正常那就应该相信监控而不是自己的经验。
第九,如果因为监控缺失等原因无法定位到根因的话,相同问题就有再出现的风险,需要做好三项工作:
今天,我和你总结分享了分析生产环境问题的套路。
第一,分析问题一定是需要依据的,靠猜是猜不出来的,需要提前做好基础监控的建设。监控的话,需要在基础运维层、应用层、业务层等多个层次进行。定位问题的时候,我们同样需要参照多个监控层的指标表现综合分析。
第二,定位问题要先对原因进行大致分类,比如是内部问题还是外部问题、CPU相关问题还是内存相关问题、仅仅是A接口的问题还是整个应用的问题,然后再去进一步细化探索,一定是从大到小来思考问题;在追查问题遇到瓶颈的时候,我们可以先退出细节,再从大的方面捋一下涉及的点,再重新来看问题。
第三,分析问题很多时候靠的是经验,很难找到完整的方法论。遇到重大问题的时候,往往也需要根据直觉来第一时间找到最有可能的点,这里甚至有运气成分。我还和你分享了我的九条经验,建议你在平时解决问题的时候多思考、多总结,提炼出更多自己分析问题的套路和拿手工具。
最后,值得一提的是,定位到问题原因后,我们要做好记录和复盘。每一次故障和问题都是宝贵的资源,复盘不仅仅是记录问题,更重要的是改进。复盘时,我们需要做到以下四点:
你有没有遇到过什么花了很长时间才定位到的,或是让你印象深刻的问题或事故呢?我是朱晔,欢迎在评论区与我留言分享你的想法,也欢迎你把这篇文章分享给你的朋友或同事,一起交流。