目录
安装环境
问题分析
后续补充
git 下载相关例子代码
git clone https://github.com/feiskyer/linux-perf-examples
案例由三个容器组成,一个MySql数据库应用,一个商品搜索应用,一个数据处理应用
其中商品搜索应用提供了HTTP借口
/, 返回Index Page
/db/insert/products/, 插入指定数据量的商品信息
/products/, 查询指定商品的信息,并返回处理事件
案例的整体结构如下
运行并检查docker容器中的应用
make run
docker run --name=mysql -itd -p 10000:80 -m 800m feisky/mysql:5.6
32f22ce141ba1279ce97cf6f6c00a51c3640edd494e01141bb2ef316ac579110
docker run --name=dataservice -itd --privileged feisky/mysql-dataservice
de3b1b96cabc40275b9a0c0b5773c87def36cb6334bab16d3f374c9312193971
docker run --name=app --network=container:mysql -itd feisky/mysql-slow
83c5dce8e2b20f1470f3b952b2858618d2de56c04d9c3933a5dd270d06825d6d
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
83c5dce8e2b2 feisky/mysql-slow "python /app.py" 3 seconds ago Up 3 seconds app
de3b1b96cabc feisky/mysql-dataservice "python /dataservi..." 4 seconds ago Up 3 seconds dataservice
32f22ce141ba feisky/mysql:5.6 "docker-entrypoint..." 4 seconds ago Up 4 seconds 3306/tcp, 0.0.0.0:10000->80/tcp mysql
检查MySql是否初始化完成
docker logs -f mysql
2019-01-23 04:54:55 1 [Note] InnoDB: Compressed tables use zlib 1.2.11
2019-01-23 04:54:55 1 [Note] InnoDB: Using Linux native AIO
2019-01-23 04:54:55 1 [Note] InnoDB: Using CPU crc32 instructions
2019-01-23 04:54:55 1 [Note] InnoDB: Initializing buffer pool, size = 5.0M
2019-01-23 04:54:55 1 [Note] InnoDB: Completed initialization of buffer pool
2019-01-23 04:54:55 1 [Note] InnoDB: Highest supported file format is Barracuda.
2019-01-23 04:54:55 1 [Note] InnoDB: 128 rollback segment(s) are active.
2019-01-23 04:54:55 1 [Note] InnoDB: Waiting for purge to start
2019-01-23 04:54:55 1 [Note] InnoDB: 5.6.42 started; log sequence number 1625997
2019-01-23 04:54:55 1 [Note] Server hostname (bind-address): '*'; port: 3306
2019-01-23 04:54:55 1 [Note] IPv6 is available.
2019-01-23 04:54:55 1 [Note] - '::' resolves to '::';
2019-01-23 04:54:55 1 [Note] Server socket created on IP: '::'.
2019-01-23 04:54:55 1 [Warning] Insecure configuration for --pid-file: Location '/var/run/mysqld' in the path is accessible to all OS users. Consider choosing a different directory.
2019-01-23 04:54:55 1 [Warning] 'proxies_priv' entry '@ root@32f22ce141ba' ignored in --skip-name-resolve mode.
2019-01-23 04:54:55 1 [Note] Event Scheduler: Loaded 0 events
2019-01-23 04:54:55 1 [Note] mysqld: ready for connections.
Version: '5.6.42-log' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)
检查商品搜索应用的web请求是否正常
curl http://[IP]: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 http://[IP]:10000/products/geektime
Got data: () in 4.605960845947266 sec
#为了避免在分析的过程中客户端请求结束,把curl放到一个循环里,每次查询结束后,等待2秒,再开始新查询请求
while true; do curl http://[IP]:10000/products/geektime; sleep5; done
通过top可以看出,负载很高了,但CPU使用率并不高,iowait很高
Tasks: 85 total, 2 running, 49 sleeping, 0 stopped, 0 zombie
%Cpu(s): 1.7 us, 4.7 sy, 0.0 ni, 32.1 id, 61.5 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 1008936 total, 186748 free, 578168 used, 244020 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 271684 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
31609 systemd+ 20 0 771836 48444 4068 S 2.6 4.8 0:16.81 mysqld
31685 root 20 0 24348 5172 644 S 0.6 0.5 0:04.79 python
31764 root 20 0 670152 415096 1708 S 0.6 41.1 3:38.22 python
观察I/O情况,使用iostat,iotop看磁盘情况
用iostat看,io util已经到100%了
iotop看占用最高的是mysql进程
通过pidstat看也是mysql进程占用很高
iostat -d -x 1
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.20 0.17 0.31 48.33 35.26 344.42 0.72 66.49 18.94 92.89 1417.13 68.79
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.00 241.41 0.00 115830.30 0.00 959.60 11.31 50.23 50.23 0.00 4.19 101.11
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.00 121.00 0.00 108988.00 0.00 1801.45 7.55 62.26 62.26 0.00 8.25 99.80
iotop
419 be/4 root 0.00 B/s 3.66 K/s 0.00 % 0.00 % systemd-journald
419 be/4 root 105.41 K/s 7.81 K/s 0.00 % 0.03 % systemd-journald
31937 be/4 root 0.00 B/s 0.00 B/s 0.00 % 0.01 % [kworker/0:2-events_power_efficient]
419 be/4 root 101.28 K/s 3.90 K/s 0.00 % 4.59 % systemd-journald
Total DISK READ : 76.09 M/s | Total DISK WRITE : 0.00 B/s
Actual DISK READ: 76.09 M/s | Actual DISK WRITE: 30.44 K/s
TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
31917 be/4 systemd- 76.09 M/s 0.00 B/s 0.00 % 97.14 % mysqld --log_bin=on --sync_binlog=1
32081 be/4 root 3.80 K/s 0.00 B/s 0.00 % 2.69 % python /usr/sbin/iotop
349 be/3 root 0.00 B/s 0.00 B/s 0.00 % 0.15 % [jbd2/vda1-8]
pidstat -d 1
01:27:24 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s Command
01:27:25 PM 0 419 0.00 3.96 0.00 systemd-journal
01:27:25 PM 0 1956 3.96 3.96 0.00 rsyslogd
01:27:25 PM 999 31609 117607.92 0.00 0.00 mysqld
01:27:25 PM 0 31685 0.00 3.96 0.00 python
01:27:25 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s Command
01:27:26 PM 999 31609 115846.46 0.00 0.00 mysqld
01:27:26 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s Command
01:27:27 PM 0 419 0.00 4.00 0.00 systemd-journal
01:27:27 PM 0 31461 12920.00 0.00 0.00 bash
01:27:27 PM 999 31609 38096.00 0.00 0.00 mysqld
01:27:27 PM 0 32153 636.00 0.00 0.00 sleep
通过strace分析程序,发现mysql有大量的读取操作
通过lsof看,是在不停的读 products.MYD 文件
strace -p 31609 -f
[pid 31917] read(37, "LfkC3vZPjtppqkYw14qPdtJyBNqJrrl2"..., 24576) = 24576
[pid 31917] read(37, "GYCfRdvn30wABejDBdBtSItHqfJXoHeH"..., 131072) = 131072
[pid 31917] read(37, "i95J5XIXsiTO4JtZZuhDrAe66aOfesVq"..., 20480) = 20480
[pid 31917] read(37, "8utFoYnF89gMbLB9bX1oNifFF0IQMcRQ"..., 131072) = 131072
[pid 31917] read(37, "V3x2yLQMscEJHLuh4d6LpZqOApXFJaLO"..., 24576) = 24576
pstree -p 31609
mysqld(31609)─┬─{mysqld}(31834)
├─{mysqld}(31835)
├─{mysqld}(31836)
├─{mysqld}(31837)
├─{mysqld}(31838)
├─{mysqld}(31839)
├─{mysqld}(31840)
├─{mysqld}(31841)
├─{mysqld}(31842)
├─{mysqld}(31843)
├─{mysqld}(31845)
├─{mysqld}(31846)
├─{mysqld}(31847)
├─{mysqld}(31848)
├─{mysqld}(31849)
├─{mysqld}(31850)
├─{mysqld}(31851)
├─{mysqld}(31852)
├─{mysqld}(31853)
├─{mysqld}(31854)
└─{mysqld}(31917)
lsof -p 31609
。。。
mysqld 31609 systemd-bus-proxy 30uW REG 253,1 98304 945464 /var/lib/mysql/mysql/slave_master_info.ibd
mysqld 31609 systemd-bus-proxy 31uW REG 253,1 98304 945466 /var/lib/mysql/mysql/slave_worker_info.ibd
mysqld 31609 systemd-bus-proxy 32uW REG 253,1 98304 945462 /var/lib/mysql/mysql/slave_relay_log_info.ibd
mysqld 31609 systemd-bus-proxy 33u REG 253,1 2048 945452 /var/lib/mysql/mysql/event.MYI
mysqld 31609 systemd-bus-proxy 34u REG 253,1 0 945453 /var/lib/mysql/mysql/event.MYD
mysqld 31609 systemd-bus-proxy 35u sock 0,9 0t0 696303 protocol: TCPv6
mysqld 31609 systemd-bus-proxy 36u REG 253,1 105472 945531 /var/lib/mysql/test/products.MYI
mysqld 31609 systemd-bus-proxy 37u REG 253,1 512440000 945532 /var/lib/mysql/test/mysqld 31609 systemd-bus-proxy 30uW REG 253,1 98304 945464 /var/lib/mysql/mysql/slave_master_info.ibd
mysqld 31609 systemd-bus-proxy 31uW REG 253,1 98304 945466 /var/lib/mysql/mysql/slave_worker_info.ibd
mysqld 31609 systemd-bus-proxy 32uW REG 253,1 98304 945462 /var/lib/mysql/mysql/slave_relay_log_info.ibd
mysqld 31609 systemd-bus-proxy 33u REG 253,1 2048 945452 /var/lib/mysql/mysql/event.MYI
mysqld 31609 systemd-bus-proxy 34u REG 253,1 0 945453 /var/lib/mysql/mysql/event.MYD
mysqld 31609 systemd-bus-proxy 35u sock 0,9 0t0 696303 protocol: TCPv6
mysqld 31609 systemd-bus-proxy 36u REG 253,1 105472 945531 /var/lib/mysql/test/products.MYI
mysqld 31609 systemd-bus-proxy 37u REG 253,1 512440000 945532 /var/lib/mysql/test/products.MYD
/var/lib/mysql/test/products.MYD
这个分析前面有个37u,表示mysqld是以读写的方式访问文件的
MYD文件,是MyISAM引擎用来存储表数据的文件
文件名就是数据表的名字
这个文件的父目录,就是数据库的名字
也就是说,mysqld在读取数据库test中的products表
可以执行如下命令,查看mysqld在管理数据库test时的存储文件,由于MySql运行在容器中,需要通过docker exec到容器中查看
docker exec -it mysql ls /var/lib/mysql/test
db.opt products.MYD products.MYI products.frm
/var/lib/mysql/test目录中有四个文件,每个文件的作用分别是
MYD 文件用来存储表的数据
MYI 文件用来存储表的索引
frm 文件用来存储表的元信息(如表结构)
opt 文件用来存储数据库的元信息(如字符集,字符校验规则等)
查看mysql的数据文件,看看这些正在读的是不是mysql正在使用的文件
docker exec -i -t mysql mysql -e 'show global variables like "%datadir%"';
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| datadir | /var/lib/mysql/ |
+---------------+-----------------+
根据上述路径可以看到,正在读的products.MYD 是mysql正在使用的文件
当然用lsof就可以确认了,这里是用mysql的视角确认的
进入mysql命令行
docker exec -i -t mysql mysql
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
show processlist;
+-----+------+-----------------+------+---------+------+--------------+-----------------------------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-----+------+-----------------+------+---------+------+--------------+-----------------------------------------------------+
| 165 | root | localhost | NULL | Query | 0 | init | show processlist |
| 167 | root | 127.0.0.1:47650 | test | Query | 2 | Sending data | select * from products where productName='geektime' |
+-----+------+-----------------+------+---------+------+--------------+-----------------------------------------------------+
在以上的输出中
db, 表示数据库的名字
Command,表示SQL类型
Time, 表示执行时间
State, 表示状态
Info, 包含了完整的SQL语句
多执行几次 show processlist命令,可以发现
select * from products where productName='geektime'
这条SQL语句的执行时间比较长
用explain分析执行情况
use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> explain select * from products where productName='geektime';
+----+-------------+----------+------+---------------+------+---------+------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+------+---------------+------+---------+------+-------+-------------+
| 1 | SIMPLE | products | ALL | NULL | NULL | NULL | NULL | 10000 | Using where |
+----+-------------+----------+------+---------------+------+---------+------+-------+-------------+
1 row in set (0.00 sec)
explain的结果中有几个比较重要的字段
select_type, 表示查询类型,这里的SIMPLE表示此查询不包括UNION查询或子查询
table, 表示数据表的名字,这里是products
type, 表示查询的类型,这里的ALL表示全表查询,但索引查询应该是index类型才对
possible_keys,表示可能选用的索引,这里是NULL
key, 表示确切会使用的索引,这里也是NULL
rows, 表示查询索淼的行数,这里是10000
根据返回的信息可以判断出,查询没有索引,导致每次查询都扫描10000行数据
需要给这个表的相应字段增加索引,首先看下表的结构
show create table products;
| products | CREATE TABLE `products` (
`id` int(11) NOT NULL,
`productCode` text NOT NULL COMMENT '产品代码',
`productName` text NOT NULL COMMENT '产品名称',
`productLine` text NOT NULL COMMENT '产品线',
`productScale` text NOT NULL,
`productVendor` text NOT NULL,
`productDescription` text NOT NULL,
`quantityInStock` smallint(6) NOT NULL COMMENT '库存',
`buyPrice` decimal(10,2) NOT NULL COMMENT '价格',
`MSRP` decimal(10,2) NOT NULL COMMENT '建议零售价',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC |
给productName字段增加索引,但执行报错了,因为productName是一个BLOB/TEXT类型的字段,需要设置一个长度,所以要给这种字段创建索引,就必须制定一个前缀长度
CREATE INDEX idx_products_name ON products(productName);
ERROR 1170 (42000): BLOB/TEXT column 'productName' used in key specification without a key length
有专门的算法,即通过计算前缀长度的选择性,来确定索引的长度,这里简单使用一个固定的值64,来为这个字段创建索引
CREATE INDEX idx_products_name ON products(productName(64));
Query OK, 10000 rows affected (9.29 sec)
Records: 10000 Duplicates: 0 Warnings: 0
再看curl的那个中端,现在查询时间已经变成1-2毫秒了,所以这次的性能问题就是没有索引导致的慢查询
Got data: () in 0.001811981201171875 sec
Got data: () in 0.0017011165618896484 sec
Got data: () in 0.001672983169555664 sec
目前为止,慢查询的问题已经接近了
对于这个案列,除了MySql和商品搜索应用外,还有一个DataService应用,这个应用其实是一个严重影响MySql性能的干扰应用,抛开上述的优化方法,这个案例还有一种优化方法,也就是停止DataService应用
首先删除掉数据库索引,回到原来状态,然后停止DataService应用,看优化效果
docker exec -i -t mysql mysql
use test;
DROP INDEX idx_products_name on products;
Query OK, 10000 rows affected (4.74 sec)
Records: 10000 Duplicates: 0 Warnings: 0
再看curl终端,这次执行速度又变慢了
Got data: () in 4.618531227111816 sec
Got data: () in 4.709952354431152 sec
现在停止DataService应用
docker rm -f dataservice
dataservice
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
83c5dce8e2b2 feisky/mysql-slow "python /app.py" 20 hours ago Up 20 hours app
32f22ce141ba feisky/mysql:5.6 "docker-entrypoint..." 20 hours ago Up 20 hours 3306/tcp, 0.0.0.0:10000->80/tcp mysql
根据文章说最终结果是速度变快了,变成0.1秒了,但我这里没看到任何变化
用vmstat看,仍然是有大量的io读取操作,具体原因待查
vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 1 0 69208 2072 364072 0 0 142 35 8 12 0 0 99 0 0
0 1 0 64808 2064 368812 0 0 114496 0 358 452 2 6 0 92 0
2 0 0 87576 1904 345024 0 0 110520 0 465 503 1 8 0 91 0
0 0 0 75144 2052 358796 0 0 68800 20 396 500 2 5 19 74 0
0 1 0 64208 2196 369012 0 0 28512 0 219 432 2 2 80 16 0
0 1 0 63316 2196 370052 0 0 110672 0 362 498 2 6 0 92 0