给系统做诊断,犹如给人看病,需要根据各种现象,抽丝剥茧,排除各种可能,最后选择可能的选项,然后再通过验证,去论证观点;原因确定后,再针对原因进行改进。可见,诊断不是一件容易的事情,考察的是一个人的综合能力,包括基础知识是否扎实,实战经验是否丰富。诊断出系统故障的根因,且能够药到病除,这是大神才有的手段。
在极客时间上,学习过《linux性能调优》算是对Linux下的系统诊断,做了一个全面的描述,可是作者手段倒是高超,只是看过了如果没有勤加实践,很容易忘记,很希望有个短小精悍一点的指导手册来作为应用诊断的地图。这次在极客时间的每日一课中看到《如何快速对应用系统做一个360度的画像诊断?》(作者:冯忠旗 京东数科高级架构师)的文章,刚好满足了这方面的需求,大呼过瘾,干货满满,我这篇文章是对这个视频的一个整理和扩充。这篇文章的角度尽可能的把握重点,不可能面面俱到。如果所有细节都说清楚了,整体脉络反而可能看不清了。
一 为什么要诊断
诊断一般是发生在系统上线后,突然遇到了CPU暴增,内存被耗尽,数据库连接池被耗尽,线程死锁,页交换频繁这些问题,最终就是影响了系统的性能,这里面的性能包括系统的吞吐量和相应时间。这时候需要老中医来对系统号号脉了。
二 如何进行诊断
现在大型些的系统,大部分运行在Linux系统上,在Linux系统上,任何应用都是个进程,而在Linux上,任何东西在Linux操作系统层面都是以文件形式存储的,进程也不例外,所以我们诊断的对象在Linux上就是进程和线程。
2.1 Proc 信息查看
先来看看Linux下的进程存储,需要重点关注/proc这个目录,这个目录其实一种特殊的,由软件创建的文件系统,内核使用它向外界导出信息的,只存在内存中,不占用外存空间。
在Linux下找一个nginx的进程来举例查看:
#系统程序名
/proc/$pid/exe
#启动命令行
/proc/$pid/cmdline
#环境变量
/proc/$pid/environ
#操作文件或网络连接[inode],可以到/proc/net/tcp里面对应
/proc/$pid/fd
#内存映射,这个有些特殊情况下非常有用,比如占用内存过大分析
cat /proc/$pid/maps
#查看线程的堆栈信息,很有用
cat /proc/$pid/task/$taskId/stack
2.2 CPU
如果系统的性能不行,包括吞吐量或者响应性,则看看CPU占用高不高,如果CPU占用不高,说明系统并没有忙于计算,可能忙于IO。一般来讲IO的占用和CPU是呈反向的,CPU高时候,说明系统忙于计算,那么此系统的必定IO比较低;如果IO比较高,系统在进行IO的时候是,一般是让出CPU的。
这里面的IO需要关注: 磁盘IO,驱动程序IO(这个目前没看过不明白),内存的换页率,网络IO。
如果CPU,内存,IO,网络带宽都不高,系统性能还是有问题,那么可能是系统被阻塞了,比如CPU在等待哪个锁,也可能等待接口返回,还有就是可能发生了上下文的切换等。
CPU资源的争夺将导致性能问题,主要是锁竞争,锁进制,导致上下文频繁切换,表现就是cpu的内核态的CPU利用率偏高。
2.3 缓存内存磁盘的速度对比
CPU 和L1 L2 内存速率对比:
图片来自:https://manybutfinite.com/post/what-your-computer-does-while-you-wait/
上图说明,L1缓存,延迟大概是1ns即3个时钟周期,L2 缓存大概是4.7ns即是14个时钟周期,内存访问大概是83ns,即250个时钟周期。
上图的文章中有个有趣的举例:假如一个时钟周期为1秒,那么从L1高速缓存读取数据就像从桌子上拿起一张纸(3秒),L2 缓存相当于从附近的书架拿一本书(14秒), 系统内存相当于占用4分钟的路程,相当于到办公楼下面买一瓶饮料(可能需要4分钟),访问磁盘,相当于离开办公楼,到地球其他地方进行了长达一年零三个月的旅行,由此可知,与指令条数相比,缓存命中不命中对程序性能的提升更加重要。
网络IO,由于要经过复杂的网络环境,所以网络的访问速度比本次磁盘的速度更慢。
2.4 内存
最大的问题是内存不足,导致swap区被占用,从而产生性能问题,对于Java程序来说,我们更多关注GC就可以了。
2.5 连接
系统对外部依赖的中间件,接口比如数据库连接,如果数据量过大或连接过大都会影响性能。
数量量过大会造成TCP的阻塞,根据TCP的一些算法会自动调整发送窗口的大小,导致性能更差。
2.6 Java异常
Java的异常捕获和处理是非常耗费资源的。如果程序在高频率地进行异常捕获和处理也会影响到性能。
三 快速诊断
3.1 资源诊断
资源包括CPU,内存,IO,网络这几个方面。
3.1.1 CPU平均负载
用top命令或uptime命令查看平均负载,这个负载是1分钟内,5分钟内,15分钟内的负载情况。
load average: 2.57, 2.50, 2.56
简单来说,平均负载是指单位时间内,系统处于可运行状态和不可中断状态的平均进程数量,也就是活跃的进程数,这个和CPU的使用率没有直接关系。可运行状态的进程,是指正在使用CPU或正在等待CPU的进程,也就是我们ps命令看到的,处于R状态(Running 或Runnable)进程。
不可中断状态的进程,则是正处于内核态关键流程中的进程,不可终端,ps中处于D状态(Uniterruptible Sle也称为Disk Sleep)的进程。不可中断状态实际上是系统对进程和硬件设备的保护,保证磁盘数据和进程数据一致性。
--摘自《Linux性能优化实战》
平均负载多少合适,这个没有绝对的值,和cpu的个数有关系,通过:
cat /proc/cpuinfo |grep "model name"|wc -l
命令查看CPU的逻辑核心数,如果平均负载超过了CPU数量的70%,就需要检查CPU问题了。
比如同样负载为2,在4个cpu上,表示有50%的CPU空闲;在2个CPU上,意味着所有的CPU刚好被完全占用,在一个CPU上,则表示一半的进程进制不到CPU。
CPU另外一个关注点就是关注内核态cpu和用户态的CPU占用,下图中us表示用户态的cpu占用29.2% 内核态CPU占用23.3%。
两个命令比较关键:
#每5s打印一次所有cpu使用情况
mpstat -P ALL 5
#打印 哪个进程占用cpu高 间隔5秒钟输出一组数据 -u表示cpu情况
pidstat -u 5 1
3.2 内存占用
通过一下命令来直接定位具体的进程的内存占用情况:
-r 表示查看内存情况。
pidstat -r -p pid 1 3
查看某个进程的IO占用情况,-d表示磁盘查看情况。
pidstat -p pid -d 1 2
- kB_rd/s : 每秒进程从磁盘读取的数据量(以kB为单位)
- kB_wr/s : 每秒进程向磁盘写的数据量(以kB为单位)
#也可以同样查看内存,cpu,io情况
#如果swpd占用比较高,在大数据系统的应用中,大多都禁用swap。
vmstat 1 5
3.3 网络问题的定位
我们可以用strace来跟踪系统的执行和系统的调用。在linux中,进程不能直接访问硬件,要访问硬件,必须从用户态进入到内核态,通过系统调用访问硬件,strace可以跟踪到进程的系统调用,包括参数,返回值和耗费的时间,可以用来排除一些系统调用原因的问题。
#下面的含义是 跟踪28979进程的所有系统调用(-e trace=all),并统计系统调用的花费时间,以及开始时间(并以可视化的时分秒格式显示),最后将记录结果存在output.txt文件里面。
strace -o output.txt -T -tt -e trace=all -p pid
网络IO也可以通过cat /proc/interrupts 看到网络的中断响应情况,我们需要长期观察的话可以通过命令:
watch cat /proc/interrupts
这个命令要查看cpu的中断响应是否均衡,在Java中很少出现这种cpu的中断不平衡问题。
3.4 磁盘IO查看
磁盘的IO可以通过dstat命令来查看,非常清楚看到IO情况:
#pidstat 可以查看具体的进程的IO情况,方便我们具体的定位。
pidstat -d 1 2
3.4 网络连接
我们和外网系统可能有交互,比如连接数据库,用netstat命令查看网络连接情况:
#我常用netstat -antp,这个o可以查看网络的具体连接状态
netstat -anop|grep 3306
连接数过多常也是一个故障点之一。
对于数据库连接数比较多,可能原因是系统不停连接数据库,或者存在慢查询,导致连接数过多的问题。
我们重点关注:TIME-WAIT和CLOSE-WAIT两种状态,其中TIME-WAIT表示主动关闭,CLOSE-WAIT表示被动关闭,如果客户端的并发量持续很高,那么TIME-WAIT数量很多,则可能是客户端连不上服务器端,需要重点关注下。
3.5 线程异常情况
线程异常关注两个问题:1. 线程状态;2.线程的连接数。
系统设计的时候要考虑资源的限制,避免系统的资源使用过度而崩溃,比如控制线程池的最大的连接数。
当系统的SY值过高的,表示Linux要耗费大量时间进行上下文的切换,在Java中,造成这种问题的最可能原因是创建的线程数比较多,这些线程不断地处于阻塞,锁等待,IO等待,状态变化过程中,这就产生了大量的上下文切换。Java在创建线程的时候,会使用堆外内存,在一定情况下,会看到系统报:
unable to create new native thread
这时候就是Java系统创建太多的线程所致,如果想创建更多线程,和想像中的相反,是要缩小些Java的内存,这样可以让OS有更多内存来创建线程,或者是缩小Java的线程里面的数据的大小。
查看线程的方法很多:
cat /proc/pid/status
top -bH -d 3 -p pid
pstree -p pid|wc -l
pstack pid |head -1
线程状态:
Blocked
Waiting
Time_Waiting
New_Runnable
Terminated
重点关注Blocked状态和Time_waiting状态:
3.6 CPU过载诊断
对于Java来说,主要通过一下方法来定位:
- 通过TOP命令或pidstat 命令来定位CPU占用过高的进程。
- top -p pid 后按住shift+H,显示线程情况,观察定位到具体线程号。
- 通过printf "%x\n" 线程id来将线程ID转成16进制的线程ID,因为在java的线程栈中,线程号是16进制展示的。
- 通过jstack -l pid |grep -A 20 16进程线程号,来分析线程具体执行的什么内容。
3.7 OOM
原因:
1.资源不够。
- 申请对象太多,频繁,内存耗尽。
3.特定资源被耗尽,比如频繁创建线程,比如频繁发起网络连接等。
定位方法: - 确认内存分配的大小,通过jmap -heap pid 来查看分配的Java内存情况。
2.找到最耗费内存的对象,通过:jmap -histo:live pid |more 来找到。 - 确认资源是否被耗尽,pstree,netstat看线程或连接被占用完。
- 内存分析最好用dump,用MAT等工具进行分析,需要进行一次Full GC,可能会影响生产系统:
jmap -dump:format=b,file=test.hprof pid
3.8 JVM的GC问题
GC会导致程序的临时暂停,频繁GC必然会导致系统的响应延迟问题,所以最好开启GC日志,方便分析问题。
可以通过jstat命令去做基本的GC分析。
3.9 日志的诊断
检查日志的异常,Error,可以快速了解系统运行情况,比如监控系统,或一些日志分析工具来查看。