环境准备
预先安装 docker、sysstat 、git、make 等工具,如 apt install docker.io sysstat
操作和分析
案例由 Python 应用 +Redis 两部分组成。其中,Python 应用是一个基于 Flask 的应用,它会利用 Redis ,来管理应用程序的缓存,并对外提供三个 HTTP 接口:
/:返回 hello redis;
/init/:插入指定数量的缓存数据,如果不指定数量,默认的是 5000 条;
缓存的键格式为 uuid:
缓存的值为 good、bad 或 normal 三者之一
/get_cache/<type_name>:查询指定值的缓存数据,并返回处理时间。其中,type_name 参数只支持 good, bad 和 normal(也就是找出具有相同 value 的 key 列表)。
案例需要两台虚拟机,其中一台用作案例分析的目标机器,运行 Flask 应用,它的 IP 地址是 xxx.xxx.xxx.xxx;而另一台作为客户端,请求缓存查询接口
在第一个终端中,执行下面的命令,运行本次案例要分析的目标应用。正常情况下,你应该可以看到下面的输出:
# 注意下面的随机字符串是容器ID,每次运行均会不同,并且你不需要关注它
docker run --name=redis -itd -p 10000:80 feisky/redis-server
docker run --name=app --network=container:redis -itd feisky/redis-app
再运行 docker ps 命令,确认两个容器都处于运行(Up)状态:
docker ps
第二个终端,使用 curl 工具,访问应用首页。如果你看到 hello redis 的输出,说明应用正常启动:
curl http://xxx.xxx.xxx.xxx:10000/
继续在终端二中,执行下面的 curl 命令,来调用应用的 /init 接口,初始化 Redis 缓存,并且插入 5000 条缓存信息。这个过程比较慢,比如我的机器就花了十几分钟时间。耐心等一会儿后,你会看到下面这行输出:
# 案例插入5000条数据,在实践时可以根据磁盘的类型适当调整,比如使用SSD时可以调大,而HDD可以适当调
curl http://xxx.xxx.xxx.xxx:10000/init/5000
继续执行下一个命令,访问应用的缓存查询接口。如果一切正常,你会看到如下输出
curl http://xxx.xxx.xxx.xxx:10000/get_cache
这个接口调用居然要花3秒!这么长的响应时间,显然不能满足实际的应用需求。
要把 curl 命令放到一个循环里来执行。你可以在终端二中,继续执行下面的命令:
$ while true; do curl http://xxx.xxx.xxx.xxx:10000/get_cache; done
终端一中执行 top 命令,分析系统的 CPU 使用情况:
CPU的 iowait 比较高,已经达到了 30%;而各个进程的 CPU 使用率都不太高,最高的 python 和 redis-server ,也分别只有 19和 12。
综合 top 的信息,最有嫌疑的就是 iowait。所以,接下来还是要继续分析,是不是 I/O 问题。
执行下面的 iostat 命令,查看有没有 I/O 性能问题:
iostat -d -x 1
磁盘 sda 每秒的写数据(wkB/s)为 1.3MB,I/O 使用率(%util)是 70。看来,虽然有些 I/O 操作,但并没导致磁盘的 I/O 瓶颈。
案例问题是从 Redis 缓存中查询数据慢。对查询来说,对应的 I/O 应该是磁盘的读操作,但刚才我们用 iostat 看到的却是写操作。虽说 I/O 本身并没有性能瓶颈,但这里的磁盘写也是比较奇怪的。为什么会有磁盘写呢?那我们就得知道,到底是哪个进程在写磁盘。
运行下面的 pidstat 命令
pidstat -d 1
I/O 最多的进程是 PID 为 4355的 redis-server,并且它也刚好是在写磁盘。这说明,确实是 redis-server 在进行磁盘写。
执行 strace 命令
strace -f -T -tt -p 4355
运行 lsof 命令
lsof -p 4355
描述符编号为 3 的是一个 pipe 管道,5 号是 eventpoll,7 号是一个普通文件,而 8 号是一个 TCP socket。
我们就找出了 Redis 正在进行写入的文件,也知道了产生大量 I/O 的原因。