本篇文章是性能篇的最后一篇文章,算是一个学习笔记吧,当中的例子也是从别的文章里面摘录的,主要用来讲解如何使用和查看对应的指标。这一篇主要介绍文件系统,说的更加具体点其实是磁盘这个点。
笔者还是按照:基础知识----》常用命令和工具----〉排查思路的方式进行了整理。
一、基础知识
1. 文件系统:在磁盘的基础上,提供了一个用来管理文件的树状结构,是对存储设备上的文件,进行组织管理的机制。
为了方便管理,Linux 文件系统为每个文件都分配两个数据结构,索引节点(index node)和目录项(directory entry)。它们主要用来记录文件的元信息和目录结构,索引节点是每个文件的唯一标志,而目录项维护的正是文件系统的树状结构。目录项和索引节点的关系是多对一,你可以简单理解为,一个文件可以有多个别名。
索引节点:简称为 inode,用来记录文件的元数据,比如 inode 编号、文件大小、访问权限、修改日期、数据的位置等。索引节点和文件一一对应,它跟文件内容一样,都会被持久化存储到磁盘中。所以记住,索引节点同样占用磁盘空间。
目录项:简称为 dentry,用来记录文件的名字、索引节点指针以及与其他目录项的关联关系。多个关联的目录项,就构成了文件系统的目录结构。不过,不同于索引节点,目录项是由内核维护的一个内存数据结构,所以通常也被叫做目录项缓存。
2. 虚拟文件系统 VFS(Virtual File System):为了支持各种不同的文件系统,Linux 内核在用户进程和文件系统的中间,又引入了一个抽象层。VFS 定义了一组所有文件系统都支持的数据结构和标准接口,这样,用户进程和内核中的其他子系统,只需要跟 VFS 提供的统一接口进行交互就可以了,而不需要再关心底层各种文件系统的实现细节。
3. 文件系统I/O: VFS 提供了一组标准的文件访问接口,这些接口以系统调用的方式,提供给应用程序使用,例如:open()、read()、write(),可以分为下面四类。
一类,是否利用标准库缓存,可以把文件 I/O 分为缓冲 I/O 与非缓冲 I/O。
1. 缓冲 I/O,是指利用标准库缓存来加速文件的访问,而标准库内部再通过系统调度访问文件。
2. 非缓冲 I/O,是指直接通过系统调用来访问文件,不再经过标准库缓存。
二类,是否利用操作系统的页缓存,可以把文件 I/O 分为直接 I/O 与非直接 I/O。
1. 直接 I/O,是指跳过操作系统的页缓存,直接跟文件系统交互来访问文件,通常系统调用中,指定 O_DIRECT 标志。
2. 非直接 I/O 正好相反,文件读写时,先要经过系统的页缓存,然后再由内核或额外的系统调用,真正写入磁盘。
三类,应用程序是否阻塞自身运行,可以把文件 I/O 分为阻塞 I/O 和非阻塞 I/O
1. 阻塞 I/O,是指应用程序执行 I/O 操作后,如果没有获得响应,就会阻塞当前线程,自然就不能执行其他任务。
2. 非阻塞 I/O,是指应用程序执行 I/O 操作后,不会阻塞当前的线程,可以继续执行其他的任务,随后再通过轮询或者事件通知的形式,获取调用的结果。
四类,是否等待响应结果,可以把文件 I/O 分为同步和异步 I/O:
1. 同步 I/O,是指应用程序执行 I/O 操作后,要一直等到整个 I/O 完成后,才能获得 I/O 响应。
2. 异步 I/O,是指应用程序执行 I/O 操作后,不用等待完成和完成后的响应,而是继续执行就可以。
等到这次 I/O 完成后,响应会用事件通知的方式,告诉应用程序。
4. 磁盘的性能指标:
1. 使用率:是指磁盘处理 I/O 的时间百分比。过高的使用率(比如超过 80%),通常意味着磁盘 I/O 存在性能瓶颈。
2. 饱和度:是指磁盘处理 I/O 的繁忙程度。过高的饱和度,意味着磁盘存在严重的性能瓶颈。当饱和度为 100% 时,磁盘无法接受新的 I/O 请求。
3. IOPS(Input/Output Per Second):是指每秒的 I/O 请求数。
4. 吞吐量:是指每秒的 I/O 请求大小。
5. 响应时间:是指 I/O 请求从发出到收到响应的间隔时间。
二、常用命令和工具
df // 查看磁盘空间
# -h 更好的可读性,查看/dev/sda1使用的磁盘空间
$ df -h /dev/sda1
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 29G 3.1G 26G 11% /
# -i,查看索引节点的磁盘空间
$ df -i /dev/sda1
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda1 3870720 157460 3713260 5% /
2.缓存大小查看
内核使用 Slab 机制,管理目录项和索引节点的缓存,/proc/meminfo 只给出了 Slab 的整体大小,具体到每一种 Slab 缓存,还要查看 /proc/slabinfo 这个文件。
# 查看Slab的整体大小
$ cat /proc/meminfo | grep -E "SReclaimable|Cached"
Cached: 748316 kB
SwapCached: 0 kB
SReclaimable: 179508 kB
# 查看每一种 Slab的缓存大小
$ cat /proc/slabinfo | grep -E '^#|dentry|inode'
# name : tunables : slabdata
xfs_inode 0 0 960 17 4 : tunables 0 0 0 : slabdata 0 0 0
...
ext4_inode_cache 32104 34590 1088 15 4 : tunables 0 0 0 : slabdata 2306 2306 0hugetlbfs_inode_cache 13 13 624 13 2 : tunables 0 0 0 : slabdata 1 1 0
sock_inode_cache 1190 1242 704 23 4 : tunables 0 0 0 : slabdata 54 54 0
shmem_inode_cache 1622 2139 712 23 4 : tunables 0 0 0 : slabdata 93 93 0
proc_inode_cache 3560 4080 680 12 2 : tunables 0 0 0 : slabdata 340 340 0
inode_cache 25172 25818 608 13 2 : tunables 0 0 0 : slabdata 1986 1986 0
dentry 76050 121296 192 21 1 : tunables 0 0 0 : slabdata 5776 5776 0
# 查看缓存类型的使用大小
# 按下c按照缓存大小排序,按下a按照活跃对象数排序
$ slabtop
Active / Total Objects (% used) : 277970 / 358914 (77.4%)
Active / Total Slabs (% used) : 12414 / 12414 (100.0%)
Active / Total Caches (% used) : 83 / 135 (61.5%)
Active / Total Size (% used) : 57816.88K / 73307.70K (78.9%)
Minimum / Average / Maximum Object : 0.01K / 0.20K / 22.88K
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
69804 23094 0% 0.19K 3324 21 13296K dentry
16380 15854 0% 0.59K 1260 13 10080K inode_cache
58260 55397 0% 0.13K 1942 30 7768K kernfs_node_cache
485 413 0% 5.69K 97 5 3104K task_struct
1472 1397 0% 2.00K 92 16 2944K kmalloc-2048
3. iostat // 查看io的指标
# r/s:每秒发送给磁盘的读请求数
# w/s:每秒发送给磁盘的写请求数
# rkB/s:每秒从磁盘读取的数据量
# wkB/s:每秒向磁盘写入的数据量
# rrqm/s :每秒合并的读请求数
# wrqm/s:每秒合并的写请求数
# r_await: 读请求处理完成等待时间,包括:队列中等待时间+设备处理的时间,单位毫秒
# w_await: 写请求处理完成等待时间,包括:队列中等待时间+设备处理的时间,单位毫秒
# aqu-sz:平均请求队列长度
# rareq-sz:平均读请求大小,单位KB
# wareq-sz:平均写请求大小,单位KB
# svctm:处理I/O请求所需要的平均时间,不包括等待时间,单位毫秒
# %util:磁盘处理I/O时间的百分比
# -d -x表示显示所有磁盘I/O的指标
# 备注:-d 选项是指显示出 I/O 的性能指标;
# -x 选项是指显示出扩展统计信息(即显示所有 I/O 指标)
$ 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
loop1 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 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
sdb 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
%util :磁盘 I/O 使用率;
r/s+ w/s :是 IOPS;
rkB/s+wkB/s :是吞吐量;
r_await+w_await :是响应时间。
4.iotop ,pidstat // 按照 I/O 大小对进程排序
$ iotop
Total DISK READ : 0.00 B/s | Total DISK WRITE : 7.85 K/s
Actual DISK READ: 0.00 B/s | Actual DISK WRITE: 0.00 B/s
TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
15055 be/3 root 0.00 B/s 7.85 K/s 0.00 % 0.00 % systemd-journald
# -d 可以显示进程对于磁盘io的情况
$ pidstat -d 1
15:08:35 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
15:08:36 0 18940 0.00 45816.00 0.00 96 python
15:08:36 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
15:08:37 0 354 0.00 0.00 0.00 350 jbd2/sda1-8
15:08:37 0 18940 0.00 46000.00 0.00 96 python
15:08:37 0 20065 0.00 0.00 0.00 1503 kworker/u4:2
5.strace // 观察系统调用情况
# 18940是进程号
$ strace -p 18940
strace: Process 18940 attached
...
mmap(NULL, 314576896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0f7aee9000
mmap(NULL, 314576896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0f682e8000
# 可以看出来这里write写了300M数据
write(3, "2018-12-05 15:23:01,709 - __main"..., 314572844
) = 314572844
munmap(0x7f0f682e8000, 314576896) = 0
write(3, "\n", 1) = 1
munmap(0x7f0f7aee9000, 314576896) = 0
close(3) = 0
# 这里可以看出来操作的是“获取 /tmp/logtest.txt.1 的状态”
stat("/tmp/logtest.txt.1", {st_mode=S_IFREG|0644, st_size=943718535, ...}) = 0
6. lsof //查看进程打开的文件情况
# FD:表示文件描述符号,
# TYPE:表示文件类型,
# NAME:表示文件路径
$ lsof -p 18940
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python 18940 root cwd DIR 0,50 4096 1549389 /
python 18940 root rtd DIR 0,50 4096 1549389 /
…
python 18940 root 2u CHR 136,0 0t0 3 /dev/pts/0
python 18940 root 3w REG 8,1 117944320 303 /tmp/logtest.txt
7. fio
# direct,表示是否跳过系统缓存。1就表示跳过系统缓存。
# iodepth,表示使用异步 I/O(asynchronous I/O,简称 AIO)时,同时发出的 I/O 请求上限。
# rw,表示 I/O 模式。我的示例中, read/write 分别表示顺序读 / 写,而 randread/randwrite 则分别表示随机读 / 写。
# ioengine,表示 I/O 引擎,它支持同步(sync)、异步(libaio)、内存映射(mmap)、网络(net)等各种 I/O 引擎。libaio 表示使用异步 I/O。
# bs,表示 I/O 的大小, 4K(这也是默认值)。
# filename,表示文件路径,当然,它可以是磁盘路径(测试磁盘性能),也可以是文件路径(测试文件系统性能)。不过注意,用磁盘路径测试写,会破坏这个磁盘中的文件系统,所以在使用前,你一定要事先做好数据备份。
# 随机读
fio -name=randread -direct=1 -iodepth=64 -rw=randread -ioengine=libaio -bs=4k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=/dev/sdb
# 随机写
fio -name=randwrite -direct=1 -iodepth=64 -rw=randwrite -ioengine=libaio -bs=4k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=/dev/sdb
# 顺序读
fio -name=read -direct=1 -iodepth=64 -rw=read -ioengine=libaio -bs=4k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=/dev/sdb
# 顺序写
fio -name=write -direct=1 -iodepth=64 -rw=write -ioengine=libaio -bs=4k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=/dev/sdb
展示报告内容:
read: (g=0): rw=read, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64
fio-3.1
Starting 1 process
Jobs: 1 (f=1): [R(1)][100.0%][r=16.7MiB/s,w=0KiB/s][r=4280,w=0 IOPS][eta 00m:00s]
read: (groupid=0, jobs=1): err= 0: pid=17966: Sun Dec 30 08:31:48 2018
read: IOPS=4257, BW=16.6MiB/s (17.4MB/s)(1024MiB/61568msec)
# slat ,是指从 I/O 提交到实际执行 I/O 的时长(Submission latency);
# clat ,是指从 I/O 提交到 I/O 完成的时长(Completion latency);
# lat ,指的是从 fio 创建 I/O 到 I/O 完成的总时长。
slat (usec): min=2, max=2566, avg= 4.29, stdev=21.76
clat (usec): min=228, max=407360, avg=15024.30, stdev=20524.39
lat (usec): min=243, max=407363, avg=15029.12, stdev=20524.26
clat percentiles (usec):
| 1.00th=[ 498], 5.00th=[ 1020], 10.00th=[ 1319], 20.00th=[ 1713],
| 30.00th=[ 1991], 40.00th=[ 2212], 50.00th=[ 2540], 60.00th=[ 2933],
| 70.00th=[ 5407], 80.00th=[ 44303], 90.00th=[ 45351], 95.00th=[ 45876],
| 99.00th=[ 46924], 99.50th=[ 46924], 99.90th=[ 48497], 99.95th=[ 49021],
| 99.99th=[404751]
# bw ,它代表吞吐量
bw ( KiB/s): min= 8208, max=18832, per=99.85%, avg=17005.35, stdev=998.94, samples=123
# iops ,其实就是每秒 I/O 的次数
iops : min= 2052, max= 4708, avg=4251.30, stdev=249.74, samples=123
lat (usec) : 250=0.01%, 500=1.03%, 750=1.69%, 1000=2.07%
lat (msec) : 2=25.64%, 4=37.58%, 10=2.08%, 20=0.02%, 50=29.86%
lat (msec) : 100=0.01%, 500=0.02%
cpu : usr=1.02%, sys=2.97%, ctx=33312, majf=0, minf=75
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued rwt: total=262144,0,0, short=0,0,0, dropped=0,0,0
latency : target=0, window=0, percentile=100.00%, depth=64
Run status group 0 (all jobs):
READ: bw=16.6MiB/s (17.4MB/s), 16.6MiB/s-16.6MiB/s (17.4MB/s-17.4MB/s), io=1024MiB (1074MB), run=61568-61568msec
Disk stats (read/write):
sdb: ios=261897/0, merge=0/0, ticks=3912108/0, in_queue=3474336, util=90.09%
备注:fio(Flexible I/O Tester)最常用的文件系统和磁盘 I/O 性能基准测试工具:https://github.com/axboe/fio
三、I/O性能的排查思路
可以按照下面的思路进行排查和定位,如下所示:
1. 用 iostat 发现磁盘 I/O 性能瓶颈;
2. 借助 pidstat ,定位出导致瓶颈的进程;
3. 分析进程的 I/O 行为;
4. 结合应用程序的原理,分析这些 I/O 的来源。
参看资料:
https://github.com/axboe/fio
https://www.thomas-krenn.com/en/wiki/Linux_Storage_Stack_Diagram
Linux 性能优化实战