一、环境准备

1、在第6节的基础上安装dstat

wget http://mirror.centos.org/centos/7/os/x86_64/Packages/dstat-0.7.2-12.el7.noarch.rpm
rpm -ivh dstat-0.7.2-12.el7.noarch.rpm

2、故障现象

# 按下数字 1 切换到所有 CPU 的使用情况,观察一会儿按 Ctrl+C 结束
$ top
top - 05:56:23 up 17 days, 16:45, 2 users, load average: 2.00, 1.68, 1.39
Tasks: 247 total, 1 running, 79 sleeping, 0 stopped, 115 zombie
%Cpu0 : 0.0 us, 0.7 sy, 0.0 ni, 38.9 id, 60.5 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 0.0 us, 0.7 sy, 0.0 ni, 4.7 id, 94.6 wa, 0.0 hi, 0.0 si, 0.0 st
... PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4340 root 20 0 44676 4048 3432 R 0.3 0.0 0:00.05 top
4345 root 20 0 37280 33624 860 D 0.3 0.0 0:00.01 app
4344 root 20 0 37280 33624 860 D 0.3 0.4 0:00.01 app
1 root 20 0 160072 9416 6752 S 0.0 0.1 0:38.59 systemd
...

①iowait太高,导致平均负载升高,并且达到了系统CPU的个数
②僵尸进程不断增多

二、iowait升高的原因分析

1、用dstat 命令同时查看cpu和i/o对比情况

(如 dstat 1 10 间隔1秒输出10组数据),通过结果可以发现iowait升高时,磁盘读请求(read)升高所以推断iowait升高是磁盘读导致

# 间隔 1 秒输出 10 组数据
$ dstat 1 10
You did not select any stats, using -cdngy by default.
--total-cpu-usage-- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai stl| read writ| recv send| in out | int csw
0 0 96 4 0|1219k 408k| 0 0 | 0 0 | 42 885
0 0 2 98 0| 34M 0 | 198B 790B| 0 0 | 42 138
0 0 0 100 0| 34M 0 | 66B 342B| 0 0 | 42 135
0 0 84 16 0|5633k 0 | 66B 342B| 0 0 | 52 177
0 3 39 58 0| 22M 0 | 66B 342B| 0 0 | 43 144
0 0 0 100 0| 34M 0 | 200B 450B| 0 0 | 46 147
0 0 2 98 0| 34M 0 | 66B 342B| 0 0 | 45 134
0 0 0 100 0| 34M 0 | 66B 342B| 0 0 | 39 131
0 0 83 17 0|5633k 0 | 66B 342B| 0 0 | 46 168
0 3 39 59 0| 22M 0 | 66B 342B| 0 0 | 37 134

实际测试截图如下

2、定位磁盘读的进程

使用top命令查看处于不可中断状态(D)的进程PID

# 观察一会儿按 Ctrl+C 结束
$ top
...
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4340 root 20 0 44676 4048 3432 R 0.3 0.0 0:00.05 top
4345 root 20 0 37280 33624 860 D 0.3 0.0 0:00.01 app
4344 root 20 0 37280 33624 860 D 0.3 0.4 0:00.01 app
...

实际测试截图

3、查看对应进程的磁盘读写情况

使用pidstat命令,加上-d参数,可以看到i/o使用情况(如 pidstat -d -p <pid> 1 3),发现处于不可中断状态的进程都没有进行磁盘读写

# -d 展示 I/O 统计数据,-p 指定进程号,间隔 1 秒输出 3 组数据
$ pidstat -d -p 4344 1 3
06:38:50 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:38:51 0 4344 0.00 0.00 0.00 0 app
06:38:52 0 4344 0.00 0.00 0.00 0 app
06:38:53 0 4344 0.00 0.00 0.00 0 app

实际测试截图

4、使用pidstat命令查看所有进程的i/o情况

但是去掉进程号,查看所有进程的i/o情况(pidstat -d 1 20),可以定位到进行磁盘读写的进程。我们知道进程访问磁盘,需要使用系统调用,
下面的重点就是找到该进程的系统调用

# 间隔 1 秒输出多组数据 (这里是 20 组)
$ pidstat -d 1 20
...
06:48:46 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:47 0 4615 0.00 0.00 0.00 1 kworker/u4:1
06:48:47 0 6080 32768.00 0.00 0.00 170 app
06:48:47 0 6081 32768.00 0.00 0.00 184 app 06:48:47 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:48 0 6080 0.00 0.00 0.00 110 app 06:48:48 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:49 0 6081 0.00 0.00 0.00 191 app 06:48:49 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command 06:48:50 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:51 0 6082 32768.00 0.00 0.00 0 app
06:48:51 0 6083 32768.00 0.00 0.00 0 app 06:48:51 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:52 0 6082 32768.00 0.00 0.00 184 app
06:48:52 0 6083 32768.00 0.00 0.00 175 app 06:48:52 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:53 0 6083 0.00 0.00 0.00 105 app
...

实际测试命令如下

[root@luoahong ~]# pidstat -d 1 20
Linux 3.10.0-957.5.1.el7.x86_64 (luoahong) 05/05/2019 _x86_64_ (2 CPU) 11:01:21 AM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command 11:01:22 AM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
11:01:23 AM 0 12174 82431.50 0.00 0.00 0 app
11:01:23 AM 0 12175 30207.50 0.00 0.00 0 app
...... 11:01:35 AM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command 11:01:36 AM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command 11:01:37 AM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
11:01:38 AM 0 12180 47103.50 0.00 0.00 0 app
11:01:38 AM 0 12181 37375.50 0.00 0.00 0 app 11:01:38 AM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
11:01:39 AM 0 12180 768512.00 0.00 0.00 51 app
11:01:39 AM 0 12181 552448.50 0.00 0.00 49 app
......
Average: 0 12179 65307.42 0.00 0.00 0 app
Average: 0 12180 65307.42 0.00 0.00 28 app
Average: 0 12181 65307.42 0.00 0.00 29 app

5、使用strace查看进程的系统调用 strace -p <pid>

strace -p 6082
strace: attach: ptrace(PTRACE_SEIZE, 6082): Operation not permitted

实际测试截图

发现报了 strace:attach :ptrace(PTRACE_SIZE,6028):Operation not peritted,说没有权限,我是使用的root权限,所以这个时候就要查看进程的状态是否正常

6、ps aux | grep <pid> 发现进程处于Z状态,已经变成了僵尸进程

所以不能进行系统调用分析了

ps aux | grep 6082
root 6082 0.0 0.0 0 0 pts/0 Z+ 13:43 0:00 [app] <defunct>

实际测试截图

7、既然top和pidstat都不能找出问题,使用基于事件记录的动态追踪工具

perf record -g
$ perf report

要是你是centos系统,在容器外面把分析记录保存,到容器里面查看结果
操作:
(1)在centos系统上运行 perf record -g ,执行一会儿按ctrl+c停止
(2)把生成的perf.data(通常文件生成在命令执行的当前目录下,当然可以通过find | grep perf.data或 find / -name perf.data查看路径)文件拷贝到容器里面分析:

docker cp perf.data app:/tpm
docker exec -i -t app bash
cd /tmp/
apt-get update && apt-get install -y linux-perf linux-tools procps
perf_4.9 report

8、看来罪魁祸首是app内部进行了磁盘的直接I/O啊!

open(disk, O_RDONLY|O_DIRECT|O_LARGEFILE, 0755)

然后观察调用栈信息,检查是否有磁盘读操作,这个案例是定位到了进行磁盘的直接读,当然也可以查看源码进行验证。因为我的centos系统用老师的 iowait镜像iowait升高不明显,所以使用的是iowait-new2镜像,可以得到想要的结果,但是这个镜像把我的系统能搞崩溃,所以我只能执行到cd /tmp/这步,下面那部就执行不下去了。但是整个分析过程还是理解了。

三、iowait升高解决方案

1、运行修改后的镜像包

# 首先删除原来的应用
$ docker rm -f app
# 运行新的应用
$ docker run --privileged --name=app -itd feisky/app:iowait-fix1

2、在用top查看一下

top
top - 14:59:32 up 19 min, 1 user, load average: 0.15, 0.07, 0.05
Tasks: 545 total, 1 running, 130 sleeping, 0 stopped, 414 zombie
%Cpu(s): 0.3 us, 25.9 sy, 0.0 ni, 65.5 id, 0.5 wa, 0.0 hi, 7.7 si, 0.0 st
KiB Mem : 8056840 total, 6620396 free, 686076 used, 750368 buff/cache
KiB Swap: 2097148 total, 2097148 free, 0 used. 7029968 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12967 root 20 0 0 0 0 Z 35.2 0.0 0:01.28 app
12968 root 20 0 0 0 0 Z 31.6 0.0 0:01.20 app
12961 root 20 0 162416 2728 1596 R 1.3 0.0 0:00.26 top
7473 root 20 0 227160 6356 4944 S 0.3 0.1 0:14.91 vmtoolsd
9369 root 20 0 851800 64620 25608 S 0.3 0.8 0:11.29 dockerd
9379 root 20 0 452964 36060 15040 S 0.3 0.4 0:12.83 containerd
9779 mysql 20 0 1119708 186364 5796 S 0.3 2.3 0:14.60 mysqld
10061 polkitd 20 0 1267680 210772 9336 S 0.3 2.6 0:15.74 mysqld
11341 root 20 0 0 0 0 S 0.3 0.0 0:06.97 kworker/0:1
1 root 20 0 43640 3952 2552 S 0.0 0.0 0:03.90 systemd

四、僵尸进程分析

1、找出父进程、然后在父进程里解决

# -a 表示输出命令行选项
# p 表 PID
# s 表示指定进程的父进程
[root@luoahong ~]# pstree -aps 12582
systemd,1 --switched-root --system --deserialize 22
└─containerd,9379
└─containerd-shim,12484 -namespace moby -workdir...
└─app,12502
└─(app,12582

2、查看修复后的源码文件

int status = 0;
for (;;) {
for (int i = 0; i < 2; i++) {
if(fork()== 0) {
sub_process();
}
}
sleep(5);
} while(wait(&status)>0);

五、僵尸进程解决方案

1、运行修复后文件打包的镜像

# 先停止产生僵尸进程的 app
$ docker rm -f app
# 然后启动新的 app
$ docker run --privileged --name=app -itd feisky/app:iowait-fix2

2、top命令查看

top
top - 13:36:25 up 2:27, 2 users, load average: 0.44, 0.23, 0.11
Tasks: 134 total, 2 running, 132 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.0 us, 12.5 sy, 0.0 ni, 83.2 id, 0.7 wa, 0.0 hi, 3.6 si, 0.0 st
KiB Mem : 8056840 total, 5975080 free, 832016 used, 1249744 buff/cache
KiB Swap: 2097148 total, 2097148 free, 0 used. 6880292 avail Mem
... PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
3198 root 20 0 4376 840 780 S 0.3 0.0 0:00.01 app
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
3 root 20 0 0 0 0 I 0.0 0.0 0:00.41 kworker/0:0
...

僵尸进程出现的原因是父进程没有回收子进程的资源出现的。解决办法是找到父进程,在父进程中处理,使用pstree查父进程,然后查看父进程的源码检查wait()/waitpid()的调用或SIGCHLD信号处理函数的注册

Linux性能优化实战学习笔记:第八讲的更多相关文章

  1. Linux性能优化实战学习笔记:第四十一讲

    一.上节回顾 上一节,我们探究了网络延迟增大问题的分析方法,并通过一个案例,掌握了如何用hping3.tcpdump.Wireshark.strace 等工具,来排查和定位问题的根源. 简单回顾一下, ...

  2. Linux性能优化实战学习笔记:第九讲

    一.中断的魅力 1.中断在生活的魅力 比如你订了一份外卖,但是不确定外卖什么时候送到,也没有别的方法了解外卖的进度,但是,配送员送外卖是不等人的,到了你这儿没人取的话,就直接走人了.所以你指能苦苦等着 ...

  3. Linux性能优化实战学习笔记:第五十八讲

    一.上节回顾 专栏更新至今,咱们专栏最后一部分——综合案例模块也要告一段落了.很高兴看到你没有掉队,仍然在积极学习思考.实践操作,并热情地分享你在实际环境中,遇到过的各种性能问题的分析思路以及优化方法 ...

  4. Linux性能优化实战学习笔记:第十八讲

    一.内存的分配和回收 1.管理内存的过程中,也很容易发生各种各样的“事故”, 对应用程序来说,动态内存的分配和回收,是既核心又复杂的一的一个逻辑功能模块.管理内存的过程中,也很容易发生各种各样的“事故 ...

  5. Linux性能优化实战学习笔记:第三十六讲

    一.上节总结回顾 上一节,我们回顾了经典的 C10K 和 C1000K 问题.简单回顾一下,C10K 是指如何单机同时处理 1 万个请求(并发连接 1 万)的问题,而 C1000K 则是单机支持处理 ...

  6. Linux性能优化实战学习笔记:第四十五讲

    一.上节回顾 专栏更新至今,四大基础模块的最后一个模块——网络篇,我们就已经学完了.很开心你还没有掉队,仍然在积极学习思考和实践操作,热情地留言和互动.还有不少同学分享了在实际生产环境中,碰到各种性能 ...

  7. Linux性能优化实战学习笔记:第五十二讲

    一.上节回顾 上一节,我们一起学习了怎么使用动态追踪来观察应用程序和内核的行为.先简单来回顾一下.所谓动态追踪,就是在系统或者应用程序还在正常运行的时候,通过内核中提供的探针,来动态追踪它们的行为,从 ...

  8. Linux性能优化实战学习笔记:第五十五讲

    一.上节回顾 上一节,我们一起学习了,应用程序监控的基本思路,先简单回顾一下.应用程序的监控,可以分为指标监控和日志监控两大块. 指标监控,主要是对一定时间段内的性能指标进行测量,然后再通过时间序列的 ...

  9. Linux性能优化实战学习笔记:第三十二讲

    一.上节总结 专栏更新至今,四大基础模块的第三个模块——文件系统和磁盘 I/O 篇,我们就已经学完了.很开心你还没有掉队,仍然在积极学习思考和实践操作,并且热情地留言与讨论. 今天是性能优化的第四期. ...

  10. Linux性能优化实战学习笔记:第四十三讲

    一.上节回顾 上一节,我们了解了 NAT(网络地址转换)的原理,学会了如何排查 NAT 带来的性能问题,最后还总结了 NAT 性能优化的基本思路.我先带你简单回顾一下. NAT 基于 Linux 内核 ...

随机推荐

  1. golang--单元测试综合实例

    实例说明: (1)一个Monster结构体,字段Name,Age,Skill (2)Monster有一个Store方法,可以将一个Monster对象序列化后保存在文件中: (3)Monster有一个R ...

  2. 现代WEB前端的性能优化

    现代WEB前端的性能优化 前言:这只是一份学习笔记. 什么是WEB前端 潜在的优化点: DNS是否可以通过缓存减少DNS查询时间? 网络请求的过程走最近的网络环境? 相同的静态资源是否可以缓存? 能否 ...

  3. hdu-6071 Lazy Running

    In HDU, you have to run along the campus for 24 times, or you will fail in PE. According to the rule ...

  4. vue中使用Ajax(axios)、vue函数中this指向问题

    Vue.js 2.0 版本推荐使用 axios 来完成 ajax 请求.Axios 是一个基于 Promise 的 HTTP 库,可以用在浏览器和 node.js 中. axios中文文档库:http ...

  5. java架构之路-(JVM优化与原理)JVM的运行时内存模型

    还是我们上次的图,我们上次大概讲解了类加载子系统的执行过程,验证,准备,解析,初始化四个过程.还有我们的双亲委派机制. 我们这次来说一下运行时内存模型.上一段小代码. public class Mai ...

  6. ElasticSearch简介(二)——简单查询

    返回所有记录 使用 GET 方法,直接请求/Index/_search,就会返回所有记录. GET /accounts/_search{    "took": 683,    &q ...

  7. WPF xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

    <Button Grid.Row="1" Content="Load Data" BorderBrush="Black" Border ...

  8. [Tomcat源码分析] Eclipse中搭建Apache Tomcat源码调试环境

    网上很多文章都推荐使用Ant下载编译,但本地实践中屡屡失败,无法下载. 后来参考 https://blog.csdn.net/xiongyouqiang/article/details/7894107 ...

  9. EntityFramework优化:SQL语句日志

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Te ...

  10. colmap编译过程中出现,无法解析的外部符号错误 “__cdecl google::base::CheckOpMessageBuilder::ForVar1(void)”

    错误提示: >colmap.lib(matching.obj) : error LNK2019: 无法解析的外部符号 "__declspec(dllimport) public: cl ...