Linux操作系统性能之文件系统篇
本篇文章是性能篇的最后一篇文章,算是一个学习笔记吧,当中的例子也是从别的文章里面摘录的,主要用来讲解如何使用和查看对应的指标。这一篇主要介绍文件系统,说的更加具体点其实是磁盘这个点。
笔者还是按照:基础知识----》常用命令和工具----〉排查思路的方式进行了整理。
一、基础知识
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 请求从发出到收到响应的间隔时间。
二、常用命令和工具
1.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'
# 查看缓存类型的使用大小
# 按下c按照缓存大小排序,按下a按照活跃对象数排序
$ slabtop
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
%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=64fio-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 性能优化实战
(原标题:性能之文件系统篇)
留言评论