Linux I/O 性能分析思路

如何找出瘋狂打日志的進程

  1. 啟動案例應(yīng)用
docker run -v /tmp:/tmp --name=app -itd feisky/logapp 
  1. 用 top 命令觀察cpu, 內(nèi)存等使用情況
top - 07:37:52 up 7 min,  1 user,  load average: 2.74, 1.27, 0.52
Tasks: 116 total,   2 running,  56 sleeping,   0 stopped,   0 zombie
%Cpu(s):  3.6 us,  7.7 sy,  0.0 ni, 10.1 id, 78.6 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  3760660 total,   147584 free,  1160440 used,  2452636 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  2333960 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
11747 root      20   0  963312 934344   5320 D  24.9 24.8   0:36.05 python
   76 root      20   0       0      0      0 D   0.9  0.0   0:01.93 kworker/u4:2+fl
   67 root       0 -20       0      0      0 I   0.4  0.0   0:00.28 kworker/0:1H-kb

發(fā)現(xiàn)系統(tǒng) iowait 快速比較高。并且內(nèi)存大部分被buff/cache占用。

  1. 繼續(xù)用 iostat 觀察磁盤 io 情況
# -d表示顯示I/O性能指標,-x表示顯示擴展統(tǒng)計(即所有I/O指標)
iostat -x -d 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.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
vdb               0.00     0.00    0.00  260.00     0.00 116856.00   898.89   107.16  412.65    0.00  412.65   3.84  99.90

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    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
vdb               0.00     0.00    0.00  236.00     0.00 118256.00  1002.17   128.66  545.65    0.00  545.65   4.24 100.00

發(fā)現(xiàn)磁盤 sdb 使用率已經(jīng)達到 接近100%。

  1. 繼續(xù)用 pidstat 觀察進程的 io 占用情況
# pidstat 加上 -d 參數(shù),就可以顯示每個進程的 I/O 情況
pidstat -d 1
07:40:11 AM   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
07:40:12 AM     0     11747      0.00  72672.00      0.00  python

07:40:13 AM   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
07:40:14 AM     0     11166      0.00     28.00      0.00  jbd2/vdb-8
07:40:14 AM     0     11747      0.00 176160.00      0.00  python
07:40:14 AM     0     13266      0.00      4.00      0.00  sap1002

可以發(fā)現(xiàn),我們找到了占用內(nèi)存比較高的進程,應(yīng)該是這里的 python 程序。

  1. 用 strace 追蹤具體的進程。
strace -p 11747
strace: Process 11747 attached
munmap(0x7fcfcbbde000, 314576896)       = 0
write(3, "\n", 1)                       = 1
munmap(0x7fcfde7df000, 314576896)       = 0
select(0, NULL, NULL, NULL, {tv_sec=0, tv_usec=100000}) = 0 (Timeout)
getpid()                                = 1
mmap(NULL, 314576896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcfde7df000
mmap(NULL, 393220096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcfc70de000
mremap(0x7fcfc70de000, 393220096, 314576896, MREMAP_MAYMOVE) = 0x7fcfc70de000
munmap(0x7fcfde7df000, 314576896)       = 0
lseek(3, 0, SEEK_END)                   = 629145690
lseek(3, 0, SEEK_CUR)                   = 629145690
munmap(0x7fcfc70de000, 314576896)       = 0
mmap(NULL, 314576896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcfde7df000
mmap(NULL, 314576896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcfcbbde000
write(3, "2024-01-29 23:42:44,016 - __main"..., 314572844) = 314572844
munmap(0x7fcfcbbde000, 314576896)       = 0
write(3, "\n", 1)                       = 1
munmap(0x7fcfde7df000, 314576896)       = 0
select(0, NULL, NULL, NULL, {tv_sec=0, tv_usec=100000}) = 0 (Timeout)
getpid()                                = 1
mmap(NULL, 314576896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcfde7df000
mmap(NULL, 393220096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcfc70de000
mremap(0x7fcfc70de000, 393220096, 314576896, MREMAP_MAYMOVE) = 0x7fcfc70de000
munmap(0x7fcfde7df000, 314576896)       = 0
lseek(3, 0, SEEK_END)                   = 943718535
lseek(3, 0, SEEK_CUR)                   = 943718535
munmap(0x7fcfc70de000, 314576896)       = 0
close(3)                                = 0
stat("/tmp/logtest.txt.1", {st_mode=S_IFREG|0644, st_size=943718535, ...}) = 0
unlink("/tmp/logtest.txt.1")            = 0
stat("/tmp/logtest.txt", {st_mode=S_IFREG|0644, st_size=943718535, ...}) = 0
rename("/tmp/logtest.txt", "/tmp/logtest.txt.1") = 0
open("/tmp/logtest.txt", O_WRONLY|O_CREAT|O_APPEND|O_CLOEXEC, 0666) = 3
fcntl(3, F_SETFD, FD_CLOEXEC)           = 0
fstat(3, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lseek(3, 0, SEEK_END)                   = 0

從 write() 系統(tǒng)調(diào)用上,我們可以看到,進程向文件描述符編號為 3 的文件中,寫入了 300MB 的數(shù)據(jù)??吹揭粋€日志文件路徑/tmp/logtest.txt.1,可以猜測,這是一個日志回滾文件,而正在寫的日志文件路徑,則是 /tmp/logtest.txt。

  1. 可以繼續(xù)用 lsof 查看這個進程打開了哪些文件。
lsof -p 11747
COMMAND   PID USER   FD   TYPE DEVICE  SIZE/OFF   NODE NAME
python  11747 root  cwd    DIR   0,47      4096 661078 /
python  11747 root  rtd    DIR   0,47      4096 661078 /
python  11747 root    2u   CHR  136,0       0t0      3 /dev/pts/0
python  11747 root    3w   REG 253,16 244989952     14 /tmp/logtest.txt

至此,我們已經(jīng)確認進程 11747 以每次 300MB 的速度,在“瘋狂”寫日志,而日志文件的路徑是 /tmp/logtest.txt。

SQL查詢磁盤性能分析

  1. 使用 top 命令查看服務(wù)器的負載
top
top - 12:02:15 up 6 days,  8:05,  1 user,  load average: 0.66, 0.72, 0.59
Tasks: 137 total,   1 running,  81 sleeping,   0 stopped,   0 zombie
%Cpu0  :  0.7 us,  1.3 sy,  0.0 ni, 35.9 id, 62.1 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  0.3 us,  0.7 sy,  0.0 ni, 84.7 id, 14.3 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  8169300 total,  7238472 free,   546132 used,   384696 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  7316952 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
27458 999       20   0  833852  57968  13176 S   1.7  0.7   0:12.40 mysqld

可以看到 cpu0 的 iowait 比較高

  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
...
sda            273.00    0.00  32568.00      0.00     0.00     0.00   0.00   0.00    7.90    0.00   1.16   119.30     0.00   3.56  97.20

可以看到,磁盤 sda 有大量的讀操作,并且使用率也幾乎打滿。

  1. 使用 pidstat 查看具體進程的 io 使用情況
pidstat -d 1
12:04:11      UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
12:04:12      999     27458  32640.00      0.00      0.00       0  mysqld
12:04:12        0     27617      4.00      4.00      0.00       3  python
12:04:12        0     27864      0.00      4.00      0.00       0  systemd-journal

定位到了 mysqld 進程。

  1. 使用 strace 命令定位 mysql 進程的數(shù)據(jù)讀取情況,加上-f 參數(shù)
strace -f -p 27458
[pid 28014] read(38, "934EiwT363aak7VtqF1mHGa4LL4Dhbks"..., 131072) = 131072
[pid 28014] read(38, "hSs7KBDepBqA6m4ce6i6iUfFTeG9Ot9z"..., 20480) = 20480
[pid 28014] read(38, "NRhRjCSsLLBjTfdqiBRLvN9K6FRfqqLm"..., 131072) = 131072
[pid 28014] read(38, "AKgsik4BilLb7y6OkwQUjjqGeCTQTaRl"..., 24576) = 24576
[pid 28014] read(38, "hFMHx7FzUSqfFI22fQxWCpSnDmRjamaW"..., 131072) = 131072
[pid 28014] read(38, "ajUzLmKqivcDJSkiw7QWf2ETLgvQIpfC"..., 20480) = 20480

可以看到,mysql 進程有大量線程在大量讀取。

  1. 使用 lsof 查看具體是打開了哪些文件
 lsof -p 27458
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
...
mysqld  27458      999   38u   REG    8,1 512440000 2601895 /var/lib/mysql/test/products.MYD

可以看到正在讀的是 mysql 數(shù)據(jù)文件。

  1. 登錄 mysql,并且多觸發(fā)幾次查詢慢的任務(wù),可以看到正在執(zhí)行的是哪些 sql, 為了保證 SQL 語句不截斷,這里可以執(zhí)行 show full processlist 命令
mysql> show full processlist;
+----+------+-----------------+------+---------+------+--------------+-----------------------------------------------------+
| Id | User | Host            | db   | Command | Time | State        | Info                                                |
+----+------+-----------------+------+---------+------+--------------+-----------------------------------------------------+
| 27 | root | localhost       | test | Query   |    0 | init         | show full processlist                               |
| 28 | root | 127.0.0.1:42262 | test | Query   |    1 | Sending data | select * from products where productName='geektime' |
+----+------+-----------------+------+---------+------+--------------+-----------------------------------------------------+
2 rows in set (0.00 sec)
  1. 使用 explain 語句分析有問題的 sql
# 切換到test庫
mysql> use test;
# 執(zhí)行explain命令
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)

其中,有幾個比較重要的字段需要注意

  • select_type 表示查詢類型,而這里的 SIMPLE 表示此查詢不包括 UNION 查詢或者子查詢;
  • table 表示數(shù)據(jù)表的名字,這里是 products;
  • type 表示查詢類型,這里的 ALL 表示全表查詢,但索引查詢應(yīng)該是 index 類型才對;
  • possible_keys 表示可能選用的索引,這里是 NULL;
  • key 表示確切會使用的索引,這里也是 NULL;
  • rows 表示查詢掃描的行數(shù),這里是 10000。
    根據(jù)結(jié)果,我們可以確定,這條查詢語句壓根兒沒有使用索引,所以查詢時,會掃描全表,并且掃描行數(shù)高達 10000 行。響應(yīng)速度肯定會很慢。

優(yōu)化方法:給productName字段添加 index 即可。

mysql> CREATE INDEX products_index ON products (productName(64));
Query OK, 10000 rows affected (14.45 sec)
Records: 10000  Duplicates: 0  Warnings: 0
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容