Linux性能优化[一]:CPU

基础篇

1 基本概念

CPU使用率: 就是除了空闲时间外的其他时间占用CPU时间的百分比

不同CPU利用率高的不同原因:

  1. 用户CPU和Nice CPU高,说明用户态进程占用了较多的CPU,所以应当着重排查进程的性能问题
  2. 系统CPU高,说明内核态占用了很多的CPU,应该着重排查内核线程或者系统调用的性能问题
  3. I/O等待CPU高,说明等待I/O的时间比较长,所以应当着重排查系统存储是不是出现了I/O问题
  4. 软中断和硬中断高,说明软中断或硬中断的处理程序占用了较多的CPU,所以应当着重排查内核中的中断服务程序

 

CPU上下文切换:

使用vmstat查看问题系统

procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0 569576 208660 794180 3313892    0    0     0    10    0    0  1  0 98  0  0	
 0  0 569576 208528 794180 3313892    0    0     0   108 1179 1269  1  1 98  0  0	
13  0 569576 206748 794180 3313896    0    0     0     0 14926 3276922 11 52 37  0  0	
10  0 569576 205880 794180 3313900    0    0     0    20 20754 5078740 16 82  2  0  0	
 8  0 569576 205748 794180 3313904    0    0     0     0 21884 5075860 16 81  3  0  0	
11  0 569576 205708 794184 3313904    0    0     0    12 22068 4952400 16 81  3  0  0	
10  0 569576 205700 794184 3313912    0    0     0    84 19911 5093832 16 81  2  0  0	

  可以看到in 和 cs飚的非常高,r的排队进程已经增长的很快了

频繁上下文切换的系统,重调度中断(RES)会很多很多,

cat /proc/interrupts
           CPU0               CPU1       CPU2       CPU3       
RES:  666600567  675118839  669223525  657028488   Rescheduling interrupts

如果系统的上下文切换次数比较稳定,那么从数百到一万以内,都应该算是正常的。但上下文切换次数超过一万次,或者切换次数出现数量级的增长时,就很可能出现性能问题。  

上下切换也分类型:

  • 自愿上下文切换变多了,说明进程都在等待资源,可能发送了I/O等其他问题
  • 非自愿上下文切换变多了,说明进程都在强制调度,也就是都在抢CPU,说明CPU的确成了瓶颈
  • 如果自愿和非自愿上下文切换都变多了(超多1w),说明系统压力已经远远超过系统能力
  • 中断次数变多了,说明CPU被中断处理程序占用,还需要通过查看/proc/interrupts文件分析具体的中断类型

  

 

2 检查工具

pidstat 是一个专门分析进程性能的工具

安装pidstat 

yum install sysstat

pidstat 常用命令

使用pidstat进行问题定位时,以下命令常被用到:

pidstat -u 1  # 用来看cpu的使用
  • %user
  • %system
  • %guest
  • %wait: 表示进程等待CPU的时间百分比,切不能混淆top中的iowait%(表示等待I/O的cpu时间百分比)
  • %CPU
  • Command

 

pidstat -r 1   # 用来看内存的使用
  • minflt/s: 从内存中加载数据时每秒出现的小的错误的数目,这些不要求从磁盘载入内存页面
  • majflt/s - 从内存中加载数据时每秒出现的较大错误的数目,这些要求从磁盘载入内存页面。 V
  • SZ - 虚拟容量:整个进程的虚拟内存使用(kb)
  • RSS - 长期内存使用:任务的不可交换物理内存的使用量(kb) 
pidstat -d 1 # 用来看io情况的
  • kB_rd/s: 任务从硬盘上的读取速度(kb)
  • kB_wr/s: 任务向硬盘中的写入速度(kb)
  • kB_ccwr/s: 任务写入磁盘被取消的速率(kb)
  • command: 执行的命令

 

pidstat -w-1 # 用来查看上下文切换
  • cswch/s: 每秒任务主动(自愿的)切换上下文的次数,当某一任务处于阻塞等待时,将主动让出自己的CPU资源,比如I/O,内存等系统资源不足的时候
  • nvcswch/s: 每秒任务被动(不自愿的)切换上下文的次数,CPU分配给某一任务的时间片已经用完,因此将强迫该进程让出CPU的执行权,比如大量进程都在抢CPU时,就容易出现非自愿上下文切换。

    

其他有用的参数

-t
# 显示进程中的线程情况,在一些CPU高并发情况下,都是子线程占用的非常高

-p
#  追加一个pid 就能看到这个进程的状态

  

   

以上命令以1秒为信息采集周期,分别获取cpu、内存和磁盘IO的统计信息。

 

 

perf

# 实施展示系统性能
perf top

perf top -g -p <pid>
  • Overhead: 是该函数的性能事件在所有采样中的比例,用百分比来
  • Shard: 是该函数或指令所在的动态共享对象,如内核、进程名、动态链接库、内核模块名等
  • Object: 是动态共享对象的类型,比如[.]表示用户空间的可执行程序或者动态链接库,而[k]则表示内核空间
  • Symbol: 是符号名,也就是函数名。当函数名未知时,用十六进制的地址来表示。

 

 

   

# 记录文件默认是perf.data
perf record

# 记录后直接分析
perf record -ag -- sleep 2; perf report

  

   

 

execsnoop

https://raw.githubusercontent.com/brendangregg/perf-tools/master/execsnoop

观察短暂的占用资源大的进程状态

  

 

iosnoop

https://raw.githubusercontent.com/brendangregg/perf-tools/master/iosnoop

观察系统的io情况,被哪些服务占用,或者跟踪某个服务占用大量io

  

 

mpstat

# CPU的运行状态查询 !
mpstat 1 10
16时52分15秒  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
16时52分16秒  all    1.00    0.00    0.25    0.00    0.00    0.25    0.00    0.00   98.50
16时52分17秒  all    1.25    0.00    0.50    0.00    0.00    0.00    0.00    0.00   98.25

  

  

vmstat 

# 内存信息 1s一次 打印10次,以M的形式(MB)
vmstat -S M 1 10
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    556    205    775   3234    0    0     0    10    0    0  1  0 98  0  0	
  • cs(context switch) 每秒上下文切换的次数
  • in(interrupt) 每秒中断的次数
  • r(Running or Runnable) 就是队列的长度,也就是正在运行和等待CPU的进程数
  • b(Blocked)处于中断睡眠状态的进程数  

 

 

3 压力模拟工具

stress

# 磁盘io压力模拟工具
stress -i 1 --timeout 60
-i 参数,表示通过系统调用sync()来模拟I/O的问题,但这种方法并不可靠。因为sync()的本意是刷新内存缓冲区的数据到磁盘,以确保同步。如果缓存区内本来就没多少数据,那读写到磁盘中的数据也就不多,就没法产生I/O压力


# 使用stress-ng代替stress
stress-ng -i 1 --hdd 1 --timeout 60

  

 

sysbench

sysbench --threads=10 --max-time=300 threads run

  

 

ab 

# 网络压测工具
ab -c 10 -n 100 http://10.4.230.25:10000/  # 注意要加结尾加 /

  

 

 

 

 

案例篇

1 使用perf遇到无法显示函数的问题

在分析Docker容器应用或者Centos系统下的进程时,我们经常碰到的一些16进制格式的函数地址。

Failed to open /opt/bitnami/php/lib/php/extensions/opcache.so, continuing without symbols

这说明,perf找不到进程依赖的相关库,这时我们总结 4个办法:

  • 在容器外面构建相同的路径的依赖库[不推荐]
  • 在容器内运行perf[需要容器运行的时候取得特权模式] 

    可以把 /proc/sys/kernel/perf_event_paranoid 设置成-1,来允许非特权用户执行perf事件

  • 指定符号路径为容器文件系统的路径
mkdir /tmp/foo
$ PID=$(docker inspect --format {{.State.Pid}} phpfpm)
$ bindfs /proc/$PID/root /tmp/foo
$ perf report --symfs /tmp/foo

# 使用完成后不要忘记解除绑定
$ umount /tmp/foo/


# 注意这个bindfs命令是需要另装的

  

  • 在容器外分析记录并保存下来,拷贝到容器里去查看结果
    # 拷贝文件到容器内
    docker cp perf.data phpfpm:/tmp 
    $ docker exec -i -t phpfpm bash
    
    # 安装perf
    cd /tmp/ 
    $ apt-get update && apt-get install -y linux-tools linux-perf procps
    $ perf_4.9 report
    

      

 

2 如何用perf工具分析Java程序

这个问题,其实是上一个 perf 问题的延伸。 像是 Java 这种通过 JVM 来运行的应用程序,运行堆栈用的都是 JVM 内置的函数和堆栈管理。所以,从系统层面你只能看到 JVM 的函数堆栈,而不能直接得到 Java 应用程序的堆栈。

 

perf_events 实际上已经支持了 JIT,但还需要一个 /tmp/perf-PID.map 文件,来进行符号翻译。当然,开源项目 perf-map-agent 可以帮你生成这个符号表。

 

此外,为了生成全部调用栈,你还需要开启 JDK 的选项 -XX:+PreserveFramePointer。因为这里涉及到大量的 Java 知识,我就不再详细展开了。如果你的应用刚好基于 Java ,那么你可以参考 NETFLIX 的技术博客 Java in Flames (链接为 https://medium.com/netflix-techblog/java-in-flames-e763b3d32166),来查看详细的使用步骤。

 

3 perf报告中很多符号都不显示调用栈,都是swapper

 

# 由于threshold默认值为0.5 ,所以小于0.5的不显示

perf report -g graph,0.3

  

 4  如何理解perf report报告

在问题 4 的 perf report 界面中,你也一定注意到了, swapper 高达 99% 的比例。直觉来说,我们应该直接观察它才对,为什么没那么做呢?

其实,当你清楚了 swapper 的原理后,就很容易理解我们为什么可以忽略它了。

看到 swapper,你可能首先想到的是 SWAP 分区。实际上, swapper 跟 SWAP 没有任何关系,它只在系统初始化时创建 init 进程,之后,它就成了一个最低优先级的空闲任务。也就是说,当 CPU 上没有其他任务运行时,就会执行 swapper 。所以,你可以称它为“空闲任务”。

 

 

套路篇

1 CPU性能指标

 

 

2 性能工具

  首先,平均负载的案例。我们先用 uptime, 查看了系统的平均负载;而在平均负载升高后,又用 mpstat 和 pidstat ,分别观察了每个 CPU 和每个进程 CPU 的使用情况,进而找出了导致平均负载升高的进程,也就是我们的压测工具 stress。

  第二个,上下文切换的案例。我们先用 vmstat ,查看了系统的上下文切换次数和中断次数;然后通过 pidstat ,观察了进程的自愿上下文切换和非自愿上下文切换情况;最后通过 pidstat ,观察了线程的上下文切换情况,找出了上下文切换次数增多的根源,也就是我们的基准测试工具 sysbench。

  第三个,进程 CPU 使用率升高的案例。我们先用 top ,查看了系统和进程的 CPU 使用情况,发现 CPU 使用率升高的进程是 php-fpm;再用 perf top ,观察 php-fpm 的调用链,最终找出 CPU 升高的根源,也就是库函数 sqrt() 。

  第四个,系统的 CPU 使用率升高的案例。我们先用 top 观察到了系统 CPU 升高,但通过 top 和 pidstat ,却找不出高 CPU 使用率的进程。于是,我们重新审视 top 的输出,又从 CPU 使用率不高但处于 Running 状态的进程入手,找出了可疑之处,最终通过 perf record 和 perf report ,发现原来是短时进程在捣鬼。另外,对于短时进程,我还介绍了一个专门的工具 execsnoop,它可以实时监控进程调用的外部命令。

  第五个,不可中断进程和僵尸进程的案例。我们先用 top 观察到了 iowait 升高的问题,并发现了大量的不可中断进程和僵尸进程;接着我们用 dstat 发现是这是由磁盘读导致的,于是又通过 pidstat 找出了相关的进程。但我们用 strace 查看进程系统调用却失败了,最终还是用 perf 分析进程调用链,才发现根源在于磁盘直接 I/O 。

  最后一个,软中断的案例。我们通过 top 观察到,系统的软中断 CPU 使用率升高;接着查看 /proc/softirqs, 找到了几种变化速率较快的软中断;然后通过 sar 命令,发现是网络小包的问题,最后再用 tcpdump ,找出网络帧的类型和来源,确定是一个 SYN FLOOD 攻击导致的。

 

 

 

 

 利用top、vmstat和pidstat三个工具,快速定位性能问题:

 

 

 

3 有多个性能问题同时存在,要怎么选择?

        广泛使用“二八原则”,就是说80%的问题都是20%的代码导致的

4 有多种优化方法时,要如何选择

  1.  CPU性能优化
    1. 性能指标: CPU使用率,用户CPU使用率,系统CPU使用率,等待I/O的CPU使用率,软中断和硬中断的CPU使用率
    2. 平均负载(Load Average) 1 5 15分钟的平均负载,理想情况平均负载等于逻辑CPU个数
    3. 进程上下文切换 无法获取资源而导致的自愿上下文切换; 被系统强制调用导致的非自愿上下文切换
    4. CPU缓存的命中率 
  2. 应用程序优化
    1. 编译器优化
    2. 算法优化
    3. 异步处理
    4. 多线程代替多线程
    5. 善用缓存
  3. 系统优化
    1. CPU绑定
    2. CPU独占
    3. 优先级调整
    4. 为进程设置资源限制
    5. NUMA优化
    6. 中断负载均衡
  4. 千万避免过早优化
    1. 过早优化是万恶之源  

 

 

1 确定性能的量化指标

2 测试优化前的性能指标

3 测试优化后的性能指标

 

像是 Java 这种通过 JVM 来运行的应用程序,运行堆...

极客时间版权所有: https://time.geekbang.org/column/article/73738

posted @ 2019-03-26 10:02 richardzgt 阅读(...) 评论(...) 编辑 收藏