You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

157 lines
12 KiB
Markdown

2 years ago
# 第33讲 | 后台服务出现明显“变慢”,谈谈你的诊断思路?
在日常工作中应用或者系统出现性能问题往往是不可避免的除了在有一定规模的IT企业或者专注于特定性能领域的企业可能大多数工程师并不会成为专职的性能工程师但是掌握基本的性能知识和技能往往是日常工作的需要并且也是工程师进阶的必要条件之一能否定位和解决性能问题也是对你知识、技能和能力的检验。
今天我要问你的问题是,后台服务出现明显“变慢”,谈谈你的诊断思路?
## 典型回答
首先,需要对这个问题进行更加清晰的定义:
* 服务是突然变慢还是长时间运行后观察到变慢?类似问题是否重复出现?
* “慢”的定义是什么,我能够理解是系统对其他方面的请求的反应延时变长吗?
第二,理清问题的症状,这更便于定位具体的原因,有以下一些思路:
* 问题可能来自于Java服务自身也可能仅仅是受系统里其他服务的影响。初始判断可以先确认是否出现了意外的程序错误例如检查应用本身的错误日志。
对于分布式系统很多公司都会实现更加系统的日志、性能等监控系统。一些Java诊断工具也可以用于这个诊断例如通过JFR[Java Flight Recordera>),监控应用是否大量出现了某种类型的异常。
如果有,那么异常可能就是个突破点。
如果没有可以先检查系统级别的资源等情况监控CPU、内存等资源是否被其他进程大量占用并且这种占用是否不符合系统正常运行状况。](https://docs.oracle.com/javacomponents/jmc-5-4/jfr-runtime-guide/about.htm#JFRUH173)
[](https://docs.oracle.com/javacomponents/jmc-5-4/jfr-runtime-guide/about.htm#JFRUH173)
[* 监控Java服务自身例如GC日志里面是否观察到Full GC等恶劣情况出现或者是否Minor GC在变长等利用jstat等工具获取内存使用的统计信息也是个常用手段利用jstack等工具检查是否出现死锁等。
* 如果还不能确定具体问题对应用进行Profiling也是个办法但因为它会对系统产生侵入性如果不是非常必要大多数情况下并不建议在生产系统进行。
* 定位了程序错误或者JVM配置的问题后就可以采取相应的补救措施然后验证是否解决否则还需要重复上面部分过程。
](https://docs.oracle.com/javacomponents/jmc-5-4/jfr-runtime-guide/about.htm#JFRUH173)
[
## 考点分析
今天我选择的是一个常见的并且比较贴近实际应用的的性能相关问题,我提供的回答包括两部分。
* 在正面回答之前,先探讨更加精确的问题定义是什么。有时候面试官并没有表达清楚,有必要确认自己的理解正确,然后再深入回答。
* 从系统、应用的不同角度、不同层次,逐步将问题域尽量缩小,隔离出真实原因。具体步骤未必千篇一律,在处理过较多这种问题之后,经验会令你的直觉分外敏感。
大多数工程师也许并没有全面的性能问题诊断机会,如果被问到也不必过于紧张,你可以向面试官展示诊断问题的思考方式,展现自己的知识和综合运用的能力。接触到一个陌生的问题,通过沟通,能够条理清晰地将排查方案逐步确定下来,也是能力的体现。
面试官可能会针对某个角度的诊断深入询问,兼顾工作和面试的需求,我会针对下面一些方面进行介绍。目的是让你对性能分析有个整体的印象,在遇到特定领域问题时,即使不知道具体细节的工具和手段,至少也可以找到探索、查询的方向。
* 我将介绍业界常见的性能分析方法论。
* 从系统分析到JVM、应用性能分析把握整体思路和主要工具。对于线程状态、JVM内存使用等很多方面我在专栏前面已经陆陆续续介绍了很多今天这一讲也可以看作是聚焦性能角度的一个小结。
如果你有兴趣进行系统性的学习我建议参考Charlie Hunt编撰的《Java Performance》或者Scott Oaks的《Java PerformanceThe Definitive Guide》。另外如果不希望出现理解偏差最好是阅读英文版。
## 知识扩展
首先,我们来了解一下业界最广泛的性能分析方法论。
根据系统架构不同分布式系统和大型单体应用也存在着思路的区别例如分布式系统的性能瓶颈可能更加集中。传统意义上的性能调优大多是针对单体应用的调优专栏的侧重点也是如此Charlie Hunt曾将其方法论总结为两类
](https://docs.oracle.com/javacomponents/jmc-5-4/jfr-runtime-guide/about.htm#JFRUH173)
[* 自上而下。从应用的顶层,逐步深入到具体的不同模块,或者更近一步的技术细节单元,找到可能的问题和解决办法。这是最常见的性能分析思路,也是大多数工程师的选择。
](https://docs.oracle.com/javacomponents/jmc-5-4/jfr-runtime-guide/about.htm#JFRUH173)* [](https://docs.oracle.com/javacomponents/jmc-5-4/jfr-runtime-guide/about.htm#JFRUH173)
[自下而上。从类似CPU这种硬件底层判断类似](https://docs.oracle.com/javacomponents/jmc-5-4/jfr-runtime-guide/about.htm#JFRUH173)[Cache-Miss](https://en.wikipedia.org/wiki/CPU_cache#Cache_miss)之类的问题和调优机会,出发点是指令级别优化。这往往是专业的性能工程师才能掌握的技能,并且需要专业工具配合,大多数是移植到新的平台上,或需要提供极致性能时才会进行。
例如将大数据应用移植到SPARC体系结构的硬件上需要对比和尽量释放性能潜力但又希望尽量不改源代码。
我所给出的回答,首先是试图排除功能性错误,然后就是典型的自上而下分析思路。
第二,我们一起来看看自上而下分析中,各个阶段的常见工具和思路。需要注意的是,具体的工具在不同的操作系统上可能区别非常大。
**系统性能分析**中CPU、内存和IO是主要关注项。
对于CPU如果是常见的Linux可以先用top命令查看负载状况下图是我截取的一个状态。
![](https://static001.geekbang.org/resource/image/3b/01/3b927b63bec67f99e8dd72860a292601.png)
可以看到其平均负载load average的三个值分别是1分钟、5分钟、15分钟非常低并且暂时看并没有升高迹象。如果这些数值非常高例如超过50%、60%),并且短期平均值高于长期平均值,则表明负载很重;如果还有升高的趋势,那么就要非常警惕了。
进一步的排查有很多思路例如我在专栏第18讲曾经问过怎么找到最耗费CPU的Java线程简要介绍步骤
* 利用top命令获取相应pid“-H”代表thread模式你可以配合grep命令更精准定位。
```
top H
```
* 然后转换成为16进制。
```
printf "%x" your_pid
```
* 最后利用jstack获取的线程栈对比相应的ID即可。
当然还有更加通用的诊断方向利用vmstat之类查看上下文切换的数量比如下面就是指定时间间隔为1收集10次。
```
vmstat -1 -10
```
输出如下:
![](https://static001.geekbang.org/resource/image/ab/a0/abd28cb4a771365211e1a01d628213a0.png)
如果每秒上下文cs[context switch](https://en.wikipedia.org/wiki/Context_switch)切换很高并且比系统中断高很多insystem [](https://en.wikipedia.org/wiki/Interrupt)[interrupt](https://en.wikipedia.org/wiki/Interrupt)),就表明很有可能是因为不合理的多线程调度所导致。当然还需要利用[pidstat](https://linux.die.net/man/1/pidstat)等手段,进行更加具体的定位,我就不再进一步展开了。
除了CPU内存和IO是重要的注意事项比如
* 利用free之类查看内存使用。
* 或者进一步判断swap使用情况top命令输出中Virt作为虚拟内存使用量就是物理内存Res和swap求和所以可以反推swap使用。显然JVM是不希望发生大量的swap使用的。
* 对于IO问题既可能发生在磁盘IO也可能是网络IO。例如利用iostat等命令有助于判断磁盘的健康状况。我曾经帮助诊断过Java服务部署在国内的某云厂商机器上其原因就是IO表现较差拖累了整体性能解决办法就是申请替换了机器。
讲到这里,如果你对系统性能非常感兴趣,我建议参考[Brendan Gregg](http://www.brendangregg.com/linuxperf.html)提供的完整图谱,我所介绍的只能算是九牛一毛。但我还是建议尽量结合实际需求,免得迷失在其中。
![](https://static001.geekbang.org/resource/image/93/40/93aa8c4516fd2266472ca4eab1b0cc40.png)
对于**JVM层面的性能分析**,我们已经介绍过非常多了:
* 利用JMC、JConsole等工具进行运行时监控。
* 利用各种工具,在运行时进行堆转储分析,或者获取各种角度的统计数据(如[jstat](https://docs.oracle.com/javase/7/docs/technotes/tools/share/jstat.html) -gcutil分析GC、内存分带等
* GC日志等手段诊断Full GC、Minor GC或者引用堆积等。
这里并不存在放之四海而皆准的办法,具体问题可能非常不同,还要看你是否能否充分利用这些工具,从种种迹象之中,逐步判断出问题所在。
对于**应用**[**Profiling**](https://en.wikipedia.org/wiki/Profiling_(computer_programming)),简单来说就是利用一些侵入性的手段,收集程序运行时的细节,以定位性能问题瓶颈。所谓的细节,就是例如内存的使用情况、最频繁调用的方法是什么,或者上下文切换的情况等。
我在前面给出的典型回答里提到一般不建议生产系统进行Profiling大多数是在性能测试阶段进行。但是当生产系统确实存在这种需求时也不是没有选择。我建议使用JFR配合[JMC](http://www.oracle.com/technetwork/java/javaseproducts/mission-control/java-mission-control-1998576.html)来做Profiling因为它是从Hotspot JVM内部收集底层信息并经过了大量优化性能开销非常低通常是低于 **2%**并且如此强大的工具也已经被Oracle开源出来
所以JFR/JMC完全具备了生产系统Profiling的能力目前也确实在真正大规模部署的云产品上使用过相关技术快速地定位了问题。
它的使用也非常方便你不需要重新启动系统或者提前增加配置。例如你可以在运行时启动JFR记录并将这段时间的信息写入文件
```
Jcmd <pid> JFR.start duration=120s filename=myrecording.jfr
```
然后使用JMC打开“.jfr文件”就可以进行分析了方法、异常、线程、IO等应有尽有其功能非常强大。如果你想了解更多细节可以参考相关[指南](https://blog.takipi.com/oracle-java-mission-control-the-ultimate-guide/)。
今天我从一个典型性能问题出发从症状表现到具体的系统分析、JVM分析系统性地整理了常见性能分析的思路并且在知识扩展部分从方法论和实际操作的角度让你将理论和实际结合相信一定可以对你有所帮助。
## 一课一练
关于今天我们讨论的题目你做到心中有数了吗? 今天的思考题是Profiling工具获取数据的主要方式有哪些各有什么优缺点。
请你在留言区写写你对这个问题的思考,我会选出经过认真思考的留言,送给你一份学习奖励礼券,欢迎你与我一起讨论。
你的朋友是不是也在准备面试呢?你可以“请朋友读”,把今天的题目分享给好友,或许你能帮到他。