CPU篇
平均负载
本篇文章是我看了极客时间《Linux性能优化实战》《趣谈网络协议》专栏做的一个总结
当系统变慢时,我们经常用uptime或者top命令来查看系统的负载情况,如下命令所示
[root@VM-0-14-centos ~]# uptime
15:30:04 up 108 days, 23:29, 1 user, load average: 0.07, 0.05, 0.05
值 | 含义 |
15:30:04 | 当前时间 |
up 108 days, 23:29 | 系统运行时间 |
1 user | 正在登陆用户数 |
load average: 0.07, 0.05, 0.05 | 过去1分钟, 5分钟,15分钟的平均负载 |
什么是平均负载?
平均负载是指单位时间内,系统处于可运行状态和不可中断状态的平均进程数
可运行状态:正在使用CPU或者等待CPU的进程,即处于Running或Runnable状态
不可中断状态:进程不能被中断,例如进程向磁盘读写数据时,为了维护数据的一致性。在得到磁盘的回复之前,它是不能被打断的(会释放cpu,但是不会响应kill命令等)
CPU的理想状态就是每个CPU上都刚好运行着一个进程,这样可以让CPU得到更充分的利用
当平均负载为2时,对CPU意味着什么呢?
- 在有1个CPU的系统上,意味着有一半进程竞争不到CPU
- 在有2个CPU的系统上,意味着所有CPU刚好被利用
- 在有4个CPU的系统上,意味着CPU有50%的空闲
所以要评判负载是否合理,需要先知道CPU个数,可以通过如下命令获取
[root@VM-0-14-centos ~]# grep 'model name' /proc/cpuinfo | wc -l
2
平均负载为多少时比较合理呢?
其实并没有一个准确的值,我们需要根据历史的监控数据作出相应的判断,看是否有明显升高的趋势。一般情况下当平均负载高于 CPU 数量 70% 的时候,你就应该分析排查负载高的问题了
CPU上下文切换
Linux操作系统是一个多任务操作系统,它支持远大于CPU数量的任务同时运行。当然这些任务并不是真的在同时运行,只是在很短的时间内,CPU轮流执行这些任务,造成多任务同时运行的错觉。
CPU上下文切换就是把上一个任务运行需要的CPU上下文(寄存器和程序计数器)保存下来,加载新任务的CPU上下文到寄存器和程序计数器,然后跳转到程序计数器执行的代码位置,执行新任务
频繁的上下文切换,会将CPU都耗费在CPU上下文的保存和恢复上,导致任务运行的时间变短,最终影响系统的性能
CPU的上下文切换主要分为如下三种
- 进程上下文切换
- 线程上下文切换
- 中断上下文切换
进程上下文切换
触发进程上下文切换的几个场景如下
- 分给进程的时间片被耗尽
- 进程在资源资源不足(比如内存不足),或者等待io完成时
- 当有优先级更高的进程运行时,为了保证高优先级的进程运行,当前进程会被挂起
- 发生硬件中断时,CPU上的进程会被挂起,转而执行内核中的中断服务程序
线程上下文切换
线程是调度的基本单位,而进程则是资源拥有的基本单位
- 当进程只有一个线程时,可以认为进程等于线程
- 当进程拥有多个线程时,会共享相同的虚拟内存和全局变量等资源,这些资源在上下文切换时不需要被更改。因此同进程内的线程切换,比多进程间的切换消耗更少的资源
- 线程的私有数据,如栈和寄存器,在进行上下文切换时是需要保存的
中断上下文切换
为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行。对同一个 CPU 来说,中断处理比进程拥有更高的优先级
针对频繁上下文切换的问题,我们可以使用vmstat命令来查看,cs一列代表了上下文切换的次数
[root@VM-0-14-centos ~]# vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 212380 136000 1543228 0 0 0 16 1 2 1 1 99 0 0
system | 含义 |
in | 每秒中断的次数 |
cs | 每秒上下文切换次数 |
如果我们希望对特定的进程进行监控,可以使用pidstat -w命令
pidstat是sysstat工具的一个命令,用于监控全部或指定进程的cpu、内存、线程、设备IO等系统资源的占用情况
pidstat首次运行时显示自系统启动开始的各项统计信息,之后运行pidstat将显示自上次运行该命令以后的统计信息。用户可以通过指定统计的次数和时间来获得所需的统计信息
# 每隔3s输出一次数据
[root@VM-0-14-centos ~]# pidstat -w 3
Linux 3.10.0-1127.19.1.el7.x86_64 (VM-0-14-centos) 10/10/2021 _x86_64_ (2 CPU)
08:13:38 PM UID PID cswch/s nvcswch/s Command
08:13:41 PM 0 6 1.66 0.00 ksoftirqd/0
08:13:41 PM 0 7 0.33 0.00 migration/0
08:13:41 PM 0 9 20.27 0.00
结果中的cswch和nvcswch是我们需要重点关注的对象
cswch(自愿上下文切换):进程无法获取所需要的资源,导致的上下文切换。例如IO,内存等系统资源不足时,就会发生自愿上下文切换
nvcswch(非自愿上下文切换):进程由于时间片已到等愿意,被系统强制调度,进而发生的上下文切换。比如,当大量进程都在争抢CPU时,就容易发生非自愿上下文切换
线上CPU飙高如何排查
- 先执行top,找到CPU占用比较高的进程
- jstack 进程id > show.txt
- 找到进程中CPU占用比较高的线程,获取到线程id(执行top -p 高进程pid -H),线程id转为16进制(printf “%x” 高进程pid)
- 到show.txt文件中根据线程id查看线程的具体状态即可
分享一个我们使用PageHelper导致死循环的例子
我们有个项目每天晚上定时从数据库获取数据做操作,用PageHelper做了分页,逻辑也很简单
int pageIndex = 0;
while (true) {
// 分页获取数据
int count = getData(pageIndex, pageSize);
if (count == 0) {
break;
}
pageIndex++;
}
这个项目运行了一段时间后,CPU占用越来越高,最终定位到是这段代码死循环了。
死循环原因如下:PageHelper有个属性reasonable,当reasonable = true,offset大于总记录数时,会默认返回最后一页数据,结果就一直能获取数据,造成了死循环
CPU使用率低但是负载高
当系统负载高时,并不意味着CPU资源不足,只是意味着运行的任务过多,这些任务有可能在等待或者使用cpu,也有可能等待IO完成
当系统负载高,并且CPU使用率也比较高时,一般意味着CPU资源不足
而当系统负载高,而CPU使用率比较低时,一般有如下两种情况
- CPU频繁的进行上下文切换(比如应用中开启了太多的线程),导致任务执行的时间比较短(利用vmstat命令查看,如果cs列或in列的值很大,说明就是这种情况)
- IO任务太多,导致大量进程处于不可中断状态(利用top命令查看,cpu使用百分比中,wa状态(cpu等待io完成)的使用百分比很高时,说明就是这种情况)
CPU问题排查套路
- 先top查看系统的负载情况
- 如果负载高,CPU高,一般意味着CPU资源不足(也有可能发生死循环之类的)
- 如果负载高,CPU低,需要接着排查
3.1 执行vmstat,查看in列(每秒中断的次数)和cs列(每秒上下文切换次数)比较高,则表明CPU频繁的进行上下文切换
3.2 top发现cpu的iowait比较高(wa列的值),则利用I/0问题排查套路接着排查
I/O问题排查套路,后面会详细分析
当发现应用频繁的进行上下文切换时,用pidstat -w确定哪个进程频繁的进行上下文切换,接着用pstree pid查看进程开启的线程数量,查看线程数量是否合理。
[root@VM-0-14-centos ~]# pstree 31528
java───169*[{java}]
也可以执行如下命令查看Threads列
[root@VM-0-14-centos ~]# cat /proc/31528/status
内存篇
我们经常用free命令查看系统中内存的使用情况,默认单位是kb,使用时我们一般指定显示单位,如-m -g
[root@VM-0-14-centos ~]# free
total used free shared buff/cache available
Mem: 3880316 1979852 236792 3100 1663672 1616760
Swap: 0 0 0
可以看到输出总共有两行,分别是物理内存Mem和交换分区Swap的使用情况
值 | 含义 |
total | 总内存大小 |
used | 已使用内存大小,包含了共享内存 |
free | 未使用内存的大小 |
shared | 共享内存的大小 |
buff/cache | buff/cache 是缓存和缓冲区的大小 |
available | 可用内存大小 |
[root@VM-0-14-centos ~]# free -m
total used free shared buff/cache available
Mem: 3789 1935 228 3 1625 1576
Swap: 0 0 0
available不仅包含free,还包含了可回收的缓存,所以一般比free更大,当然并不是所有缓存都可以被回收,因为有些缓存可能正在使用中
交换分区Swap是做什么用的?
Swap说白了就是把一块磁盘空间或者一个本地文件,当成内存来使用,这样就可以增大系统的可用内存,生产环境我们一般不使用Swap分区。它包括换入和换出的过程
- 换出,将进程暂时不用的内存存储到磁盘中,并释放这些数据占用的内存
- 换入,当进程再次访问这些内存的时候,将他们从磁盘读到内存中来
怎么理解内存中的Buffer和Cache?
free命令的输出其他列都比较容易理解,Buffer和Cache分别代表什么呢?
从字面来看,Buffer是缓冲区,而Cache是缓存,两者都是数据在内存中的临时存储。那么这两种临时存储有什么区别吗?
Buffer是对磁盘数据的缓存,而Cache是文件数据的缓存,他们既会用到读请求中,也会用到写请求中
如何排查内存泄漏和内存溢出
因为Java程序都是基于堆来管理和使用内存的,所以当我们排查内存泄漏和内存溢出时,一般使用jdk自带的工具
- 使用jmap命令生成heapdump文件
- 使用Eclipse Memory Analyzer来分析对象的使用情况
IO篇
磁盘的性能指标
使用率:磁盘处理I/O的时间百分比(%util)
饱和度:磁盘处理I/O的繁忙程度(avgqu-sz,排队请求数越多越繁,和你去超市买东西一个道理)
IOPS(Input/Output Per Second):每秒的I/O请求数(r/s+w/s)
吞吐量:每秒的I/O请求大小(rkB/s+wkB/s)
响应时间:I/O请求从发出到收到响应的间隔时间(r_await+w_await)
[root@VM-0-14-centos ~]# iostat -x 1
Linux 3.10.0-1127.19.1.el7.x86_64 (VM-0-14-centos) 10/12/2021 _x86_64_ (2 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
0.60 0.00 0.53 0.06 0.00 98.81
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 2.88 0.01 4.11 0.25 32.02 15.67 0.02 4.56 8.79 4.55 0.29 0.12
scd0 0.00 0.00 0.00 0.00 0.00 0.00 18.65 0.00 0.21 0.21 0.00 0.20 0.00
值 | 含义 |
rrqm/s | 每秒合并的读请求数,%rrqm表示合并读请求的百分比 |
wrqm/s | 每秒合并的写请求数,%wrqm表示合并写请求的百分比 |
r/s | 每秒发送给磁盘的读请求数,为合并后的请求数 |
w/s | 每秒发送给磁盘的写请求数,为合并后的请求数 |
rkB/s | 每秒从磁盘读取的数据量,单位为kb |
wkB/s | 每秒向磁盘写入的数据量,单位为kb |
avgrq-sz | 每个IO的平均扇区数,即所有请求的平均大小,以扇区(512字节)为单位 |
avgqu-sz | 平均未完成的IO请求数量,即平均意义上的请求队列长度 |
await | 平均每次 I/O 操作的时间,包括队列中的等待时间和设备实际处理的时间,单位为毫秒 |
r_await | 读请求处理完成等待时间,包括队列中的等待时间和设备实际处理的时间,单位为毫秒 |
w_await | 写请求处理完成等待时间,包括队列中的等待时间和设备实际处理的时间,单位为毫秒 |
svctm | 处理I/O请求所需的平均时间(不包括等待时间),单位为毫秒。这是推断的数据,并不保证完全准确 |
%util | 磁盘处理I/O的时间百分比,即使用率,由于可能存在并行I/O,100%并不一定表明磁盘I/O饱和 |
合并请求怎么理解?
块设备有相应的调度算法。如果两个IO发生在相邻的数据块时,他们可以合并成1个IO。
你可以类比送快递,假如快递员要送的快递在2,16,5,17楼。快递员肯定是先送2,5楼的快递。再送16,17楼的快递。而不是依次送2,16,5,17楼的快递
进程I/O监测
iostat只提供磁盘整体的I/O数据,如果要观察进程的I/O情况,我们可以使用pidstat命令
[root@VM-0-14-centos ~]# pidstat -d
Linux 3.10.0-1127.19.1.el7.x86_64 (VM-0-14-centos) 10/12/2021 _x86_64_ (2 CPU)
02:06:44 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s Command
02:06:44 PM 0 1 0.14 4.00 0.40 systemd
02:06:44 PM 0 35 0.00 0.00 0.00
值 | 含义 |
UID | 用户id |
PID | 进程id |
kB_rd/s | 每秒读取的数据大小,单位kb |
kB_wr/s | 每秒发出的写请求数据大小,单位kb |
kB_ccwr/s | 每秒取消的写请求数据大小,单位kb |
查看进程具体读写的文件和内容
我们一般用strace+lsof命令来查看进程具体读写的文件和内容
strace常用来跟踪进程执行时的系统调用和所接收的信号。 在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核态模式,通 过系统调用访问硬件设备。strace可以跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间
lsof(list open files)是一个查看进程打开的文件的工具。在linux系统中,一切皆文件,所以lsof命令不仅可以查看进程打开的文件、目录,还可以查看进程的socket等相关信息
I/O问题排查套路
- top发现iowait比较高
- 用iostat查看是哪个进程读写比较高
- 通过strace+lsof找出进程读取的文件和内容,进而确定I/O出现瓶颈的原因
常用命令汇总
top
第一行(基本信息)
值 | 含义 |
16:16:26 | 当前时间 |
up 109 days, 15 min | 系统运行时间,这里表示运行了109天,15分钟 |
1 user | 正在登陆用户数 |
load average: 0.00, 0.02, 0.05 | 过去1分钟, 5分钟,15分钟的平均负载 |
第二行(任务信息)
值 | 含义 |
101 total | 进程总数 |
1 running | 正在运行的进程数 |
100 sleeping | 睡眠状态的进程数 |
0 stopped | 停止的进程数 |
0 zombie | 僵尸进程数 |
第三行(CPU使用情况)
值 | 含义 |
us | User Time,CPU执行用户进程百分比 |
sy | System Time,CPU在内核运行百分比 |
ni | Nine Time,调整进程优先级所用百分比 |
id | Idle Time,系统空闲百分比 |
wa | Waiting Time,CPU等待IO完成所用百分比 |
hi | Hard IPQ Time ,硬中断百分比 |
si | Soft IPQ Time ,软中断百分比 |
st | Steal Time,虚拟服务占用百分比 |
第四行(物理内存使用情况)
值 | 含义 |
total | 总物理内存 |
free | 空闲的物理内存 |
used | 已经使用的物理内存 |
buff/cache | 缓冲区和缓冲区占用总量 |
第六行(进程详细信息)
值 | 含义 |
PID | 进程ID |
USER | 进程所有者的用户名 |
PR | 优先级 |
NI | nice值 |
VIRT | 进程使用的虚拟内存总量,单位kb |
RES | 进程使用的,未被还出的物理内存大小,单位kb |
SHR | 共享内存大小,单位kb |
S | 进程状态 |
%CPU | CPU时间占用比 |
%MEM | 物理内存占用比 |
TIME+ | 进程总计使用的CPU时间 |
COMMAND | 命令名 |
进程状态的取值有如下几种
- D - 不可中断的睡眠态,一般表示进程正在跟硬件交互,但是此刻进程是不可中断的。不可中断,指的并不是CPU不响应外部硬件的中断,而是指进程不响应异步信号。绝大多数情况下,进程处在睡眠状态时,总是应该能够响应异步信号的。但是uninterruptible sleep 状态的进程不接受外来的任何信号,因此无法用kill杀掉这些处于D状态的进程,无论是”kill”, “kill -9″还是”kill -15″
- R – 运行态,进程在CPU的就绪队列中,正在运行或者等待运行
- S – 可中断睡眠态,进程因为等待某个事件而被挂起。当进程等待的事件发生时,会被唤醒并进入R状态
- T – 进程处于暂停或者跟踪状态
- Z – 僵尸态,表示僵尸进程,进程实际上已经结束了,但是父进程还没有回收它的资源。
交互使用
执行top命令时,可以输入交互命令
1:查看CPU每个核的使用情况
h:显示帮助画面,给出一些简短的命令总结说明
k:终止一个进程
q:退出程序
r:重新安排一个进程的优先级别
m:切换显示内存信息
t:切换显示进程和CPU状态信息
c:切换显示命令名称和完t整命令行
M:根据驻留内存大小进行排序
P:根据CPU使用百分比大小进行排序
T:根据时间/累计时间进行排序
vmstat
[root@VM-0-14-centos ~]# vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 212380 136000 1543228 0 0 0 16 1 2 1 1 99 0 0
procs | 含义 |
r | 就绪队列的长度,也就是正在运行和等待CPU的进程数 |
b | 不可中断睡眠状态的进程数 |
memory | 含义 |
swpd | 交换分区swap使用的大小 |
free | 未使用内存的大小 |
buff | buffer缓冲区的大小 |
cache | cache缓冲区的大小 |
swap | 含义 |
si | 每秒从磁盘读入swap分区的大小,如果这个值不够用了,说明这个物理内存不够用了 |
so | 每秒从swap分区写入磁盘的大小 |
io | 含义 |
bi | 块设备每秒发送的块数量 |
bo | 块设备每秒接收的块数量 |
system | 含义 |
in | 每秒中断的次数 |
cs | 每秒上下文切换次数 |
cpu | 含义 |
us | User Time,CPU执行用户进程百分比 |
sy | System Time,CPU在内核运行百分比 |
id | Idle Time,系统空闲百分比 |
wa | Waiting Time,CPU等待IO完成所用百分比 |
st | Steal Time,虚拟服务占用百分比 |
参考博客
[1]https://fredal.xin/java-error-check#toc_h3_14
使用率低但是负载高
[2]https://zoco.fun/program/cpu%E4%BD%BF%E7%94%A8%E7%8E%87%E4%BD%8E%E8%B4%9F%E8%BD%BD%E9%AB%98%E7%9A%84%E4%B8%80%E4%B8%AAcase/
[2]https://www.jianshu.com/p/347afe9ba9ee
io事务
[3]https://www.heapdump.cn/article/558214
性能优化专栏
[4]https://www.zhihu.com/column/xingnengyouhua
iostat
[5]https://bean-li.github.io/dive-into-iostat/
记一次服务端 IO 瓶颈问题定位
[6]https://testerhome.com/topics/20606
connect reset
包的格式
Connection reset
[9]https://www.jianshu.com/p/6ce9598d61fb
[10]https://tech.kujiale.com/ying-yong-pin-fan-bao-chu-cause-java-net-sockettimeoutexception-read-timed-outzen-yao-ban/