CategoryResourceRepost/极客时间专栏/Java业务开发常见错误100例/加餐/33 | 加餐3:定位应用问题,排错套路很重要.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

159 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="33 | 加餐3定位应用问题排错套路很重要" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a3/89/a386559811a46f95445534120cc39889.mp3"></audio>
你好,我是朱晔。
咱们这个课程已经更新13讲了感谢各位同学一直在坚持学习并在评论区留下了很多高质量的留言。这些留言有的是分享自己曾经踩的坑有的是对课后思考题的详细解答还有的是提出了非常好的问题进一步丰富了这个课程的内容。
有同学说这个课程的案例非常实用都是工作中会遇到的。正如我在开篇词中所说这个课程涉及的100个案例、约130个小坑有40%来自于我经历过或者是见过的200多个线上生产事故剩下的60%来自于我开发业务项目,以及日常审核别人的代码发现的问题。确实,我在整理这些案例上花费了很多精力,也特别感谢各位同学的认可,更希望你们能继续坚持学习,继续在评论区和我交流。
也有同学反馈,排查问题的思路很重要,希望自己遇到问题时,也能够从容、高效地定位到根因。因此,今天这一讲,我就与你说说我在应急排错方面积累的心得。这都是我多年担任技术负责人和架构师自己总结出来的,希望对你有所帮助。当然了,也期待你能留言与我说说,自己平时的排错套路。
## 在不同环境排查问题,有不同的方式
要说排查问题的思路,我们首先得明白是在什么环境排错。
- 如果是在自己的开发环境排查问题那你几乎可以使用任何自己熟悉的工具来排查甚至可以进行单步调试。只要问题能重现排查就不会太困难最多就是把程序调试到JDK或三方类库内部进行分析。
- 如果是在测试环境排查问题相比开发环境少的是调试不过你可以使用JDK自带的jvisualvm或阿里的[Arthas](https://github.com/alibaba/arthas)附加到远程的JVM进程排查问题。另外测试环境允许造数据、造压力模拟我们需要的场景因此遇到偶发问题时我们可以尝试去造一些场景让问题更容易出现方便测试。
- 如果是在生产环境排查问题,往往比较难:一方面,生产环境权限管控严格,一般不允许调试工具从远程附加进程;另一方面,生产环境出现问题要求以恢复为先,难以留出充足的时间去慢慢排查问题。但,因为生产环境的流量真实、访问量大、网络权限管控严格、环境复杂,因此更容易出问题,也是出问题最多的环境。
接下来,我就与你详细说说,如何在生产环境排查问题吧。
## 生产问题的排查很大程度依赖监控
其实,排查问题就像在破案,生产环境出现问题时,因为要尽快恢复应用,就不可能保留完整现场用于排查和测试。因此,是否有充足的信息可以了解过去、还原现场就成了破案的关键。这里说的信息,主要就是日志、监控和快照。
日志就不用多说了,主要注意两点:
- 确保错误、异常信息可以被完整地记录到文件日志中;
- 确保生产上程序的日志级别是INFO以上。记录日志要使用合理的日志优先级DEBUG用于开发调试、INFO用于重要流程信息、WARN用于需要关注的问题、ERROR用于阻断流程的错误。
对于监控,在生产环境排查问题时,首先就需要开发和运维团队做好充足的监控,而且是多个层次的监控。
- 主机层面对CPU、内存、磁盘、网络等资源做监控。如果应用部署在虚拟机或Kubernetes集群中那么除了对物理机做基础资源监控外还要对虚拟机或Pod做同样的监控。监控层数取决于应用的部署方案有一层OS就要做一层监控。
- 网络层面,需要监控专线带宽、交换机基本情况、网络延迟。
- 所有的中间件和存储都要做好监控不仅仅是监控进程对CPU、内存、磁盘IO、网络使用的基本指标更重要的是监控组件内部的一些重要指标。比如著名的监控工具Prometheus就提供了大量的[exporter](https://prometheus.io/docs/instrumenting/exporters/)来对接各种中间件和存储系统。
- 应用层面需要监控JVM进程的类加载、内存、GC、线程等常见指标比如使用[Micrometer](https://micrometer.io/)来做应用监控此外还要确保能够收集、保存应用日志、GC日志。
我们再来看看快照。这里的“快照”是指应用进程在某一时刻的快照。通常情况下我们会为生产环境的Java应用设置-XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath=…这2个JVM参数用于在出现OOM时保留堆快照。这个课程中我们也多次使用MAT工具来分析堆快照。
了解过去、还原现场后,接下来我们就看看定位问题的套路。
## 分析定位问题的套路
定位问题首先要定位问题出在哪个层次上。比如是Java应用程序自身的问题还是外部因素导致的问题。我们可以先查看程序是否有异常异常信息一般比较具体可以马上定位到大概的问题方向如果是一些资源消耗型的问题可能不会有异常我们可以通过指标监控配合显性问题点来定位。
一般情况下,程序的问题来自以下三个方面。
第一程序发布后的Bug回滚后可以立即解决。这类问题的排查可以回滚后再慢慢分析版本差异。
第二,外部因素,比如主机、中间件或数据库的问题。这类问题的排查方式,按照主机层面的问题、中间件或存储(统称组件)的问题分为两类。
主机层面的问题,可以使用工具排查:
- CPU相关问题可以使用top、vmstat、pidstat、ps等工具排查
- 内存相关问题可以使用free、top、ps、vmstat、cachestat、sar等工具排查
- IO相关问题可以使用lsof、iostat、pidstat、sar、iotop、df、du等工具排查
- 网络相关问题可以使用ifconfig、ip、nslookup、dig、ping、tcpdump、iptables等工具排查。
组件的问题,可以从以下几个方面排查:
- 排查组件所在主机是否有问题;
- 排查组件进程基本情况,观察各种监控指标;
- 查看组件的日志输出,特别是错误日志;
- 进入组件控制台,使用一些命令查看其运作情况。
第三因为系统资源不够造成系统假死的问题通常需要先通过重启和扩容解决问题之后再进行分析不过最好能留一个节点作为现场。系统资源不够一般体现在CPU使用高、内存泄漏或OOM的问题、IO问题、网络相关问题这四个方面。
对于CPU使用高的问题如果现场还在具体的分析流程是
- 首先在Linux服务器上运行top -Hp pid命令来查看进程中哪个线程CPU使用高
- 然后输入大写的P将线程按照 CPU 使用率排序并把明显占用CPU的线程ID转换为16进制
- 最后在jstack命令输出的线程栈中搜索这个线程ID定位出问题的线程当时的调用栈。
如果没有条件直接在服务器上运行top命令的话我们可以用采样的方式定位问题间隔固定秒数比如10秒运行一次jstack命令采样几次后对比采样得出哪些线程始终处于运行状态分析出问题的线程。
如果现场没有了我们可以通过排除法来分析。CPU使用高一般是由下面的因素引起的
- 突发压力。这类问题我们可以通过应用之前的负载均衡的流量或日志量来确认诸如Nginx等反向代理都会记录URL可以依靠代理的Access Log进行细化定位也可以通过监控观察JVM线程数的情况。压力问题导致CPU使用高的情况下如果程序的各资源使用没有明显不正常之后可以通过压测+Profilerjvisualvm就有这个功能进一步定位热点方法如果资源使用不正常比如产生了几千个线程就需要考虑调参。
- GC。这种情况我们可以通过JVM监控GC相关指标、GC Log进行确认。如果确认是GC的压力那么内存使用也很可能会不正常需要按照内存问题分析流程做进一步分析。
- 程序中死循环逻辑或不正常的处理流程。这类问题,我们可以结合应用日志分析。一般情况下,应用执行过程中都会产生一些日志,可以重点关注日志量异常部分。
对于内存泄露或OOM的问题最简单的分析方式就是堆转储后使用MAT分析。堆转储包含了堆现场全貌和线程栈信息一般观察支配树图、直方图就可以马上看到占用大量内存的对象可以快速定位到内存相关问题。这一点我们会在[第5篇加餐](https://time.geekbang.org/column/article/230534)中详细介绍。
需要注意的是Java进程对内存的使用不仅仅是堆区还包括线程使用的内存线程个数*每一个线程的线程栈和元数据区。每一个内存区都可能产生OOM可以结合监控观察线程数、已加载类数量等指标分析。另外我们需要注意看一下JVM参数的设置是否有明显不合理的地方限制了资源使用。
IO相关的问题除非是代码问题引起的资源不释放等问题否则通常都不是由Java进程内部因素引发的。
网络相关的问题一般也是由外部因素引起的。对于连通性问题结合异常信息通常比较容易定位对于性能或瞬断问题可以先尝试使用ping等工具简单判断如果不行再使用tcpdump或Wireshark来分析。
## 分析和定位问题需要注意的九个点
有些时候,我们分析和定位问题时,会陷入误区或是找不到方向。遇到这种情况,你可以借鉴下我的九个心得。
**第一,考虑“鸡”和“蛋”的问题。**比如,发现业务逻辑执行很慢且线程数增多的情况时,我们需要考虑两种可能性:
- 一是程序逻辑有问题或外部依赖慢使得业务逻辑执行慢在访问量不变的情况下需要更多的线程数来应对。比如10TPS的并发原先一次请求1s可以执行完成10个线程可以支撑现在执行完成需要10s那就需要100个线程。
- 二是有可能是请求量增大了使得线程数增多应用本身的CPU资源不足再加上上下文切换问题导致处理变慢了。
出现问题的时候,我们需要结合内部表现和入口流量一起看,确认这里的“慢”到底是根因还是结果。
**第二,考虑通过分类寻找规律。**在定位问题没有头绪的时候,我们可以尝试总结规律。
比如我们有10台应用服务器做负载均衡出问题时可以通过日志分析是否是均匀分布的还是问题都出现在1台机器。又比如应用日志一般会记录线程名称出问题时我们可以分析日志是否集中在某一类线程上。再比如如果发现应用开启了大量TCP连接通过netstat我们可以分析出主要集中连接到哪个服务。
如果能总结出规律,很可能就找到了突破点。
**第三,分析问题需要根据调用拓扑来,不能想当然。**比如我们看到Nginx返回502错误一般可以认为是下游服务的问题导致网关无法完成请求转发。对于下游服务不能想当然就认为是我们的Java程序比如在拓扑上可能Nginx代理的是Kubernetes的Traefik Ingress链路是Nginx-&gt;Traefik-&gt;应用如果一味排查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接口的问题还是整个应用的问题然后再去进一步细化探索一定是从大到小来思考问题在追查问题遇到瓶颈的时候我们可以先退出细节再从大的方面捋一下涉及的点再重新来看问题。
第三,分析问题很多时候靠的是经验,很难找到完整的方法论。遇到重大问题的时候,往往也需要根据直觉来第一时间找到最有可能的点,这里甚至有运气成分。我还和你分享了我的九条经验,建议你在平时解决问题的时候多思考、多总结,提炼出更多自己分析问题的套路和拿手工具。
最后,值得一提的是,定位到问题原因后,我们要做好记录和复盘。每一次故障和问题都是宝贵的资源,复盘不仅仅是记录问题,更重要的是改进。复盘时,我们需要做到以下四点:
- 记录完整的时间线、处理措施、上报流程等信息;
- 分析问题的根本原因;
- 给出短、中、长期改进方案包括但不限于代码改动、SOP、流程并记录跟踪每一个方案进行闭环
- 定期组织团队回顾过去的故障。
## 思考与讨论
1. 如果你现在打开一个App后发现首页展示了一片空白那这到底是客户端兼容性的问题还是服务端的问题呢如果是服务端的问题又如何进一步细化定位呢你有什么分析思路吗
1. 对于分析定位问题,你会做哪些监控或是使用哪些工具呢?
你有没有遇到过什么花了很长时间才定位到的,或是让你印象深刻的问题或事故呢?我是朱晔,欢迎在评论区与我留言分享你的想法,也欢迎你把这篇文章分享给你的朋友或同事,一起交流。