环境准备
预先安装 docker、sysstat 、git、make 等工具,如 apt install docker.io sysstat make git
操作和分析
案例总共由三个容器组成,包括一个 MySQL 数据库应用、一个商品搜索应用以及一个数据处理的应用。
其中,商品搜索应用以 HTTP 的形式提供了一个接口:
/:返回 Index Page;
/db/insert/products/:插入指定数量的商品信息;
/products/:查询指定商品的信息,并返回处理时间。
需要两台虚拟机,其中一台作为案例分析机器,运行 Flask 应用,它的 IP 地址是 xxx.xxx.xxx.xxx;另一台则是作为客户端,请求单词的热度
在第一个终端中执行下面命令,拉取本次案例所需脚本:
git clone https://github.com/feiskyer/linux-perf-examples
cd linux-perf-examples/mysql-slow
执行下面的命令,运行本次的目标应用
make run
再运行 docker ps 命令,确认三个容器都处在运行(Up)状态:
docker ps
当看到下面这个输出时,说明 MySQL 初始化完成,可以接收外部请求了:
docker logs -f mysql
而商品搜索应用则是在 10000 端口监听。你可以按 Ctrl+C ,停止 docker logs 命令;然后,执行下面的命令,确认它也已经正常运行。如果一切正常,你会看到 Index Page 的输出:
curl http://127.0.0.1:10000/
Index Page
运行 make init 命令,初始化数据库,并插入 10000 条商品信息。这个过程比较慢,比如在我的机器中,就花了十几分钟时间。耐心等待一段时间后,你会看到如下的输出:
make init
docker exec -i mysql mysql -uroot -P3306 < tables.sql
curl http://127.0.0.1:10000/db/insert/products/10000
insert 10000 lines
接着,我们切换到第二个终端,访问一下商品搜索的接口,看看能不能找到想要的商品。执行如下的 curl 命令:
curl http://xxx.xxx.xxx.xxx:10000/products/geektime
Got data: () in 15.364538192749023 sec
在终端二中,继续执行下面的命令:
while true; do curl http://xxx.xxx.xxx.xxx:10000/products/geektime; sleep 5; done
回到终端一中,分析接口响应速度慢的原因。不过,重回终端一后,你会发现系统响应也明显变慢了,随便执行一个命令,都得停顿一会儿才能看到输出。
在终端一执行 top 命令,分析系统的 CPU 使用情况:
CPU 的 iowait 都比较高,iowait 50%。而具体到各个进程, CPU 使用率并不高,最高的也只有 4%。
既然 CPU 的嫌疑不大,那问题应该还是出在了 I/O 上。我们仍然在第一个终端,按下 Ctrl+C,停止 top 命令;然后,执行下面的 iostat 命令,看看有没有 I/O 性能问题:
iostat -d -x 1
磁盘 vda 每秒的读数据为 107MB, 而 I/O 使用率高达 99% ,接近饱和,这说明,磁盘 vda 的读取确实碰到了性能瓶颈。
这些 I/O 请求到底是哪些进程呢?当然可以找我们的老朋友, pidstat。接下来,在终端一中,按下 Ctrl+C 停止 iostat 命令,然后运行下面的 pidstat 命令,观察进程的 I/O 情况:
pidstat -d 1
PID 为 8389的 mysqld 进程正在进行大量的读,找到了磁盘 I/O 瓶颈的根源,即 mysqld 进程。
为什么 mysqld 会去读取大量的磁盘数据呢?按照前面猜测,我们提到过,这有可能是个慢查询问题。
执行 strace 命令
strace -f -p 8389
线程 30173正在读取大量数据,且读取文件的描述符编号为 37。这儿的 37又对应着哪个文件呢?
lsof -p 8389
路径为 /var/lib/mysql/test/products.MYD 的文件,mysqld 在读取数据库 test 中的 products 表
MySQL 的慢查询问题,很可能是没有利用好索引导致的,那这条查询语句是不是这样呢?我们又该怎么确认,查询语句是否利用了索引呢?
docker exec -i -t mysql mysql
在 MySQL 终端中,运行下面的 explain 命令:
# 切换到test库
mysql> use test;
# 执行explain命令
mysql> explain select * from products where productName='geektime';
possible_keys 表示可能选用的索引,这里是 NULL;这条查询语句没有使用索引,定位到性能瓶颈了