Linux性能优化实战学习笔记:第二十七讲
一、案例环境描述
1、环境准备
2CPU,4GB内存
预先安装docker sysstat工具
2、温馨提示
案例中 Python 应用的核心逻辑比较简单,你可能一眼就能看出问题,但实际生产环境中的源码就复杂多了。所以,
我依旧建议,操作之前别看源码,避免先入为主,要把它当成一个黑盒来分析。这样 你可以更好把握住,怎么从系统的资源使用问题出发,分析出瓶颈
所在的应用,以及瓶颈在应用中大概的位置
3、测试环境准备
1、运行目标应用
docker run --name=app -p 10000:80 -itd feisky/word-pop
2、确认应用正常启动
[root@luoahong ~]# ps aux | grep app.py
root 10130 0.0 0.5 95700 23392 pts/0 Ss+ 10:29 0:00 python /app.py
root 10167 30.8 0.7 104924 30372 pts/0 Sl+ 10:29 4:32 /usr/local/bin/python /app.py
root 10256 0.0 0.0 112716 2288 pts/1 S+ 10:44 0:00 grep --color=auto app.py
二、故障现象
1、发现故障
1、接下来,在第二个终端中,访问案例应用的单词热度接口
[root@luoahong ~]# curl http://192.168.118.115:10000
hello world[root@luoahong ~]# curl http://192.168.118.115:10000/popularity/word
{
"popularity": 0.0,
"word": "word"
}
稍等一会儿,你会发现,这个接口居然这么长时间都没响应,究竟是怎么回事呢?我们先回到终端一来分析一下。
2、我们试试在第一个终端里,随便执行一个命令,居然也要等好久才能输出
[root@luoahong ~]# df
Filesystem 1K-blocks Used Available Use% Mounted on
devtmpfs 1995624 0 1995624 0% /dev
tmpfs 2007620 0 2007620 0% /dev/shm
tmpfs 2007620 9336 1998284 1% /run
tmpfs 2007620 0 2007620 0% /sys/fs/cgroup
/dev/sda2 50306052 29273120 21032932 59% /
tmpfs 401524 0 401524 0% /run/user/0
overlay 50306052 29273120 21032932 59% /var/lib/docker/overlay2/0bc7de96c86ea3d2fe1059ccf2dea175b05a5434cc0a73858b5292b610699530/merged
shm 65536 0 65536 0% /var/lib/docker/containers/f0b72f14052f48a2a6eaf034d11e2fea77b76250bd87863e50d2f8aeb22c9918/mounts/shm
2、故障现象
1、top
进程部分有一个 python 进程的 CPU 使用率稍微有点达到了 40.4%。虽然 40.1%并不能成为性能瓶颈,不过有点嫌疑——可能跟 iowait 的升高有关
那这个 PID 号为 10167 的 python 进程,到底是不是我们的案例应用呢?
2、然后执行下面的 ps 命令,查找案例应用 app.py 的 PID 号:
[root@luoahong ~]# ps aux | grep app.py
root 10130 0.0 0.5 95700 23392 pts/0 Ss+ 10:29 0:00 python /app.py
root 10167 30.8 0.7 104924 30372 pts/0 Sl+ 10:29 4:32 /usr/local/bin/python /app.py
root 10256 0.0 0.0 112716 2288 pts/1 S+ 10:44 0:00 grep --color=auto app.py
从 ps 的输出,你可以看到,这个 CPU 使用率较高的进程,不过先别着急分析 CPU 问题,毕竟 iowait 已经高达92%
三、分析过程
1、观察系统I/O使用情况
1、案例
iostat -d -x 1
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm r_await w_await aqu-sz rareq-sz wareq-sz svctm %util
loop0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sda 0.00 71.00 0.00 32912.00 0.00 0.00 0.00 0.00 0.00 18118.31 241.89 0.00 463.55 13.86 98.40
2、实际测试
root@luoahong ~]# iostat -d -x 1
Linux 5.1.0-1.el7.elrepo.x86_64 (luoahong) 05/30/2019 _x86_64_ (2 CPU) Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz aqu-sz %util
sda 2.14 834.77 0.01 0.44 23.88 390.85 75.84 85205.18 0.60 0.79 191.95 1123.42 0.00 0.00 0.00 0.00 0.00 0.00 14.57 10.79 Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz aqu-sz %util
sda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz aqu-sz %util
sda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz aqu-sz %util
sda 0.00 0.00 0.00 0.00 0.00 0.00 125.00 103592.00 1.00 0.79 39.18 828.74 0.00 0.00 0.00 0.00 0.00 0.00 4.83 14.90
-d 选项是指显示出I/O的性能指标;
-x 选项是指显示出扩展统计信息(即显示所有I/O指标)
1、你可以发现,磁盘 sda 的 I/O 使用率已经达到 98%接近饱和了
2、而且,写请求的响应时间高达 18 秒,每秒的写数据为32MB,虽然写磁盘碰到了瓶颈
3、这些I/O请求到底是那些进程导致的呢?
2、知道了进程PID,具体要怎么查看写的情况呢?
[root@luoahong ~]# pidstat -d 1
Linux 5.1.0-1.el7.elrepo.x86_64 (luoahong) 05/30/2019 _x86_64_ (2 CPU) 11:19:22 AM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
11:19:23 AM 0 10167 0.00 124549.02 0.00 0 python
11:19:23 AM 0 10191 0.00 0.00 0.00 108 kworker/u256:1+flush-8:0 11:19:23 AM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
11:19:24 AM 0 10167 0.00 126168.00 0.00 0 python
11:19:24 AM 0 10191 0.00 0.00 0.00 100 kworker/u256:1+flush-8:0
走到这一步,你估计觉得,接下来就很简单了,上一个案例不刚刚学过吗?无非就是,先用 strace 确认它是不是在写文件,再用 lsof 找出文件描述符对应的文件即可。
1、strace查看制定PID调用情况
[root@luoahong ~]# strace -p 10167
strace: Process 10167 attached
select(0, NULL, NULL, NULL, {0, 403619}) = 0 (Timeout)
......
stat("/usr/local/lib/python3.7/stringprep.py", {st_mode=S_IFREG|0644, st_size=12917, ...}) = 0
stat("/usr/local/lib/python3.7/stringprep.py", {st_mode=S_IFREG|0644, st_size=12917, ...}) = 0
stat("/usr/local/lib/python3.7/_bootlocale.py", {st_mode=S_IFREG|0644, st_size=1801, ...}) = 0
stat("/usr/local/lib/python3.7/_bootlocale.py", {st_mode=S_IFREG|0644, st_size=1801, ...}) =
由于 strace 的输出比较多,我们可以用 grep ,来过滤一下 write,比如:
[root@luoahong ~]# strace -p 10167 2>&1 |grep write
遗憾的是没有任何输出
2、filetop
它是 bcc 软件包的一部分,基于 Linux 内核的eBPF(extended Berkeley Packet Filters)机制,主要跟踪内核中文件的读写情况,并输出线
程 ID(TID)、读写大小、读写类型以及文件名称。
bcc的安装方法:https://github.com/iovisor/bcc
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD
echo "deb https://repo.iovisor.org/apt/$(lsb_release -cs) $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/iovisor.list
sudo apt-get update
sudo apt-get install bcc-tools libbcc-examples linux-headers-$(uname -r)
安装后,bcc 提供的所有工具,就全部安装到了/usr/share/bcc/tools 这个目录中接下来我们就用这个工具,观察一下文件的读写情况。
[root@luoahong tools]# ./filetop -C
Tracing... Output every 1 secs. Hit Ctrl-C to end 11:54:58 loadavg: 2.37 1.27 0.54 2/185 9851 TID COMM READS WRITES R_Kb W_Kb T FILE
9850 python 2 0 3662 0 R 995.txt
9850 python 2 0 3564 0 R 998.txt
9850 python 2 0 3466 0 R 986.txt
9850 python 2 0 3466 0 R 994.txt
9850 python 2 0 3222 0 R 988.txt
9850 python 2 0 3173 0 R 993.txt
9850 python 2 0 2929 0 R 992.txt
9850 python 2 0 2832 0 R 990.txt
9850 python 2 0 2734 0 R 989.txt
9850 python 2 0 2490 0 R 997.txt
9850 python 2 0 2441 0 R 999.txt
9850 python 2 0 2294 0 R 987.txt
9850 python 2 0 2246 0 R 996.txt
9850 python 2 0 2099 0 R 984.txt
9850 python 2 0 1806 0 R 985.txt
9850 python 2 0 1660 0 R 991.txt
9847 filetop 1 0 4 0 R retprobe
9847 filetop 1 0 4 0 R type
9847 filetop 2 0 2 0 R loadavg
9851 sleep 1 0 0 0 R libc-2.17.so
线程号为 514 的线程,属于哪个进程呢?
[root@luoahong tools]# ps -efT|grep 9891
root 9798 9891 9755 46 11:59 pts/0 00:00:07 /usr/local/bin/python /app.py
root 9894 9894 9805 0 12:00 pts/1 00:00:00 grep --color=auto 9891
filetop 只给出了文件名称,却没有文件路径,还得继续找啊
3、opensnoop
它同属于 bcc 软件包,可以动态跟踪内核中的 open 系统调用。这样,我们可以找出这些txt文件的路径
[root@luoahong tools]# ./opensnoop
PID COMM FD ERR PATH
9798 python 6 0 /tmp/9ef81916-828f-11e9-960a-0242ac110002/898.txt
9921 opensnoop -1 2 /usr/lib64/python2.7/encodings/ascii.so
9921 opensnoop -1 2 /usr/lib64/python2.7/encodings/asciimodule.so
9921 opensnoop 12 0 /usr/lib64/python2.7/encodings/ascii.py
9921 opensnoop 13 0 /usr/lib64/python2.7/encodings/ascii.pyc
9798 python 6 0 /tmp/9ef81916-828f-11e9-960a-0242ac110002/899.txt
9798 python 6 0 /tmp/9ef81916-828f-11e9-960a-0242ac110002/900.txt
9798 python 6 0 /tmp/9ef81916-828f-11e9-960a-0242ac110002/901.txt
9798 python 6 0 /tmp/9ef81916-828f-11e9-960a-0242ac110002/902.txt
9798 python 6 0 /tmp/9ef81916-828f-11e9-960a-0242ac110002/903.txt
9798 python 6 0 /tmp/9ef81916-828f-11e9-960a-0242ac110002/904.txt
9798 python 6 0 /tmp/9ef81916-828f-11e9-960a-0242ac110002/905.txt
9798 python 6 0 /tmp/9ef81916-828f-11e9-960a-0242ac110002/906.txt
9798 python 6 0 /tmp/9ef81916-828f-11e9-960a-0242ac110002/907.txt
9798 python 6 0 /tmp/9ef81916-828f-11e9-960a-0242ac110002/908.txt
9798 python 6 0 /tmp/9ef81916-828f-11e9-960a-0242ac110002/909.txt
9798 python 6 0 /tmp/9ef81916-828f-11e9-960a-0242ac110002/910.txt
9798 python 6 0 /tmp/9ef81916-828f-11e9-960a-0242ac110002/911.txt
9798 python 6 0 /tmp/9ef81916-828f-11e9-960a-0242ac110002/912.txt
综合 filetop 和 opensnoop ,我们就可以进一步分析了。我们可以大胆猜测,案例应用在写入1000 个 txt 文件后又把这些内容读到内存中进行处理
我们来检查一下,这个目录中是不是真的有 1000 个文件:
[root@luoahong tools]# ls /tmp/9ef81916-828f-11e9-960a-0242ac110002 |wc -l
ls: cannot access /tmp/9ef81916-828f-11e9-960a-0242ac110002: No such file or directory
0
操作后却发现,目录居然不存在了,怎么回事呢?我们回到 opensnoop 再观察一会儿
[root@luoahong tools]# ./opensnoop
PID COMM FD ERR PATH
9798 python 6 0 /tmp/d94f7bec-829c-11e9-960a-0242ac110002/351.txt
10589 opensnoop -1 2 /usr/lib64/python2.7/encodings/ascii.so
10589 opensnoop -1 2 /usr/lib64/python2.7/encodings/asciimodule.so
10589 opensnoop 12 0 /usr/lib64/python2.7/encodings/ascii.py
10589 opensnoop 13 0 /usr/lib64/python2.7/encodings/ascii.pyc
9798 python 6 0 /tmp/d94f7bec-829c-11e9-960a-0242ac110002/352.txt
9798 python 6 0 /tmp/d94f7bec-829c-11e9-960a-0242ac110002/353.txt
9798 python 6 0 /tmp/d94f7bec-829c-11e9-960a-0242ac110002/354.txt
9798 python 6 0 /tmp/d94f7bec-829c-11e9-960a-0242ac110002/355.txt
9798 python 6 0 /tmp/d94f7bec-829c-11e9-960a-0242ac110002/356.txt
9798 python 6 0 /tmp/d94f7bec-829c-11e9-960a-0242ac110002/357.txt
原来,这时的路径已经变成了另一个目录,这说明,这些目录都是应用程序动态生成的,用完就删了。
结合前面的所有分析,我们基本可以判断,案例应用会动态生成一批文件,用来临时存储数据,用完就会删除它们。但不幸的是,正是这些文件读写,引发了 I/O 的性能瓶颈,
导致整个处理过程非常慢
4、确认猜想(查看源代码)
@app.route("/popularity/<word>")
def word_popularity(word):
dir_path = '/tmp/{}'.format(uuid.uuid1())
count = 0
sample_size = 1000 def save_to_file(file_name, content):
with open(file_name, 'w') as f:
f.write(content) try:
# initial directory firstly
os.mkdir(dir_path) # save article to files
for i in range(sample_size):
file_name = '{}/{}.txt'.format(dir_path, i)
article = generate_article()
save_to_file(file_name, article) # count word popularity
for root, dirs, files in os.walk(dir_path):
for file_name in files:
with open('{}/{}'.format(dir_path, file_name)) as f:
if validate(word, f.read()):
count += 1
finally:
# clean files
shutil.rmtree(dir_path, ignore_errors=True) return jsonify({'popularity': count / sample_size * 100, 'word': word})
四、解决方案
1、问题总结
源码中可以看到,这个案例应用
1、在每个请求的处理过程中毛都会生成一批临时文件。
2、然后读入内存处理,
3、最后把整个目录删除掉
这是一种常见的利用磁盘空间处理大量数据技巧,不过,本次案例中I/O请求太重。导致磁盘I/O利用率过高
2、算法优化
要解决这一点其实就是算法优化问题,比如在内存充足时,就可以把所有的数据存放到内存中处理,这样就避免I/O的性能问题
你可以检验一下,在中断二中分别访问:http://192.168.0.10:10000/popularity/word和http://192.168.0.10:10000/popular/word对比前后的效果
http://192.168.0.10:10000/popularity/word
time curl http://192.168.0.10:10000/popularity/word
{
"popularity": 0.0,
"word": "word"
}
real 2m43.172s
user 0m0.004s
sys 0m0.007s
http://192.168.0.10:10000/popular/word
time curl http://192.168.0.10:10000/popular/word
{
"popularity": 0.0,
"word": "word"
} real 0m8.810s
user 0m0.010s
sys 0m0.000s
新的接口只要 8 秒就可以返回,明显比一开始的 3 分钟好很多
当然,这只是优化的第一步,并且方法不算完善,还可以做进一步的优化,
不过,在实际系统中,我们大都是类似的做法,先用简单的方法,尽早解决线上问题,然后在继续思考更好的优化方法
五、故障小结
今天,我们分析了一个响应过慢的单词热度案例。
首先,我们用 top、iostat,分析了系统的 CPU 和磁盘使用情况,我们发现了磁盘I/O 瓶颈,也知道了这个瓶颈是案例应用导致的。
接着,我们试着照搬上一节案例的方法,用 strace 来观察进程的系统调用,不过这次很不走运,没找到任何 write 系统调用。
于是,我们又用了新的工具,借助动态追踪工具包 bcc 中的 filetop 和 opensnoop ,找出了案例应用的问题,发现这个根源是大量读写临时文件。
找出问题后,优化方法就相对比较简单了。如果内存充足时,最简单的方法,就是把数据都放在速度更快的内存中,这样就没有磁盘 I/O 的瓶颈了。
当然,再进一步,你可以还可以利用 Trie 树等各种算法,进一步优化单词处理的效率。
Linux性能优化实战学习笔记:第二十七讲的更多相关文章
- Linux性能优化实战学习笔记:第四十一讲
一.上节回顾 上一节,我们探究了网络延迟增大问题的分析方法,并通过一个案例,掌握了如何用hping3.tcpdump.Wireshark.strace 等工具,来排查和定位问题的根源. 简单回顾一下, ...
- Linux性能优化实战学习笔记:第九讲
一.中断的魅力 1.中断在生活的魅力 比如你订了一份外卖,但是不确定外卖什么时候送到,也没有别的方法了解外卖的进度,但是,配送员送外卖是不等人的,到了你这儿没人取的话,就直接走人了.所以你指能苦苦等着 ...
- Linux性能优化实战学习笔记:第五十七讲
一.上节回顾 上一节,我带你一起梳理了常见的性能优化思路,先简单回顾一下.我们可以从系统和应用程序两个角度,来进行性能优化. 从系统的角度来说,主要是对 CPU.内存.网络.磁盘 I/O 以及内核软件 ...
- Linux性能优化实战学习笔记:第四十五讲
一.上节回顾 专栏更新至今,四大基础模块的最后一个模块——网络篇,我们就已经学完了.很开心你还没有掉队,仍然在积极学习思考和实践操作,热情地留言和互动.还有不少同学分享了在实际生产环境中,碰到各种性能 ...
- Linux性能优化实战学习笔记:第三十六讲
一.上节总结回顾 上一节,我们回顾了经典的 C10K 和 C1000K 问题.简单回顾一下,C10K 是指如何单机同时处理 1 万个请求(并发连接 1 万)的问题,而 C1000K 则是单机支持处理 ...
- Linux性能优化实战学习笔记:第四十三讲
一.上节回顾 上一节,我们了解了 NAT(网络地址转换)的原理,学会了如何排查 NAT 带来的性能问题,最后还总结了 NAT 性能优化的基本思路.我先带你简单回顾一下. NAT 基于 Linux 内核 ...
- Linux性能优化实战学习笔记:第四十四讲
一.上节回顾 上一节,我们学了网络性能优化的几个思路,我先带你简单复习一下. 在优化网络的性能时,你可以结合 Linux 系统的网络协议栈和网络收发流程,然后从应用程序.套接字.传输层.网络层再到链路 ...
- Linux性能优化实战学习笔记:第五十二讲
一.上节回顾 上一节,我们一起学习了怎么使用动态追踪来观察应用程序和内核的行为.先简单来回顾一下.所谓动态追踪,就是在系统或者应用程序还在正常运行的时候,通过内核中提供的探针,来动态追踪它们的行为,从 ...
- Linux性能优化实战学习笔记:第五十五讲
一.上节回顾 上一节,我们一起学习了,应用程序监控的基本思路,先简单回顾一下.应用程序的监控,可以分为指标监控和日志监控两大块. 指标监控,主要是对一定时间段内的性能指标进行测量,然后再通过时间序列的 ...
- Linux性能优化实战学习笔记:第五十八讲
一.上节回顾 专栏更新至今,咱们专栏最后一部分——综合案例模块也要告一段落了.很高兴看到你没有掉队,仍然在积极学习思考.实践操作,并热情地分享你在实际环境中,遇到过的各种性能问题的分析思路以及优化方法 ...
随机推荐
- C# 跨线程访问UI不报错,必须使用Invoke。
代码有时跨线程访问UI,修改按钮Enable属性不报异常.调试发现修改按钮属性的线程是Background,执行不报异常. 在窗体构造中添加 Control.CheckForIllegalCrossT ...
- Delphi - 操作Excel数据公式的实现
procedure TF_SMP_FT_NEW.RzBitBtn_StartToChangeClick(Sender: TObject); var i, j, ni, nj, iRows, iCol, ...
- 使用IDEA创建Spring Boot项目
第一步:根据1.2.3点鼠标哦: 第二步:点击Next 第三步:按红框框选,然后Next 第四步:选个保存路径,然后Next 第五步:点击Finish就大功告成了
- jmeter对tomcat性能测试
主要对tomcat的参数做一些记录(jmeter和tomcat在同一个计算机,可能引起测试误差) 我的计算机配置 4核8线程 8G内存 案例一 tomcat JVM 1.8G堆内存,无数据库操 ...
- webpack篇,结合理论与实际,加以透彻分析!
Webpack篇 开始着手项目打包的一些东西,还不是特别懂,一边学习,一边做笔记好啦. 1.webpack的概念.Webpack 是当下最热门的前端资源模块化管理和打包工具.任何形式的资源都可以视作模 ...
- RT-Thread点亮led
下载默认工程 https://www.rt-thread.org/ 配置rtconfig.h #define STM32F103RE //修改成自己的板子 #define RT_HSE_VALUE 8 ...
- Red Hat安装虚拟带库
1.安装五个包 # yum -y install lzo-devel sg3_utils lsscsi mtx mt-st mtx源码:http://sourceforge.net/ 2.下载最新版的 ...
- vue---v-model的详细解答
1.v-model:双向数据绑定的实现原理 等同于一个 v-bind 加 v-on <div id="app"> <!-- <input t ...
- 编译器与Debug的传奇:Grace Murray Hopper小传
摘要: 改变世界的程序员前辈. 来自:http://www.road2stat.com/cn/network_3c/grace_murray_hopper.html 这两天读<UNIX痛恨者手册 ...
- Django restframework 序列化之 ModelSerializer 小记
首先介绍一下相关概念 序列化器(Serializer) 1. 自定义型: 继承rest_framework.serializers.Serializer 2. 模型类型: 继承rest_frame ...