「Linux Performance」- I/O

  CREATED BY JENKINSBOT

Coding for SSDs – Part 1: Introduction and Table of Contents | Code Capsule

性能指标

注意事项:
1)注意具体场景,读写类型(顺序还是随机)、读写比例、读写大小、存储类型(有无 RAID 以及 RAID 级别、本地存储还是网络存储)等
2)不同场景的 I/O 性能指标,不能直接进行分析对比

性能工具

df => 文件系统数据的空间容量 / 查看索引节点的容量

/proc/meminfo、/proc/slabinfo / slabtop => 页缓存、目录项缓存、索引节点缓存、具体文件系统的缓存

iostat => 磁盘的 I/O 使用率、吞吐量、响应时间、IOPS

pidstat => 观察到进程的 I/O 吞吐量、块设备 I/O 的延迟

strace / lsof => 进程正在读写的文件、系统调用

filetop / opensnoop => 从内核中跟踪系统调用

性能指标和工具的联系

从指标出发

文件系统空间容量、使用量以及剩余空间 => df

索引节点容量、使用量以及剩余量 => df

页缓存和可回收Slab缓存
/proc/meminfo
sar -r、vmstat

缓冲区
/proc/meminfo
sar -r、vmstat

目录项、索引节点以及文件系统的缓存
/proc/slabinfo
slabtop更直观

磁盘/O使用率、IOPS、吞吐量、响应时间、I/O平均大小以及等待队列长度
iostat -d -x
sar -d
dstat

进程 I/O 大小以及 I/O 延迟
pidstat -d
iotop

块设备I/0事件跟踪
blktrace => blktrace -d /dev/sda -O – | blkparse -i –

进程I/O系统调用跟踪(通过系统调用跟踪进程的/0)
strace

进程块设备I/O大小跟踪(安装 bcc 软件)
biosnoop
biotop

从工具出发

iostat => 磁盘I/O使用率、IOPS、 吞吐量、响应时间、I/O平均大小以及等待队列长度

pidstat => 进程I/O大小以及I/O延迟

sar => 磁盘I/O使用率、IOPS、吞吐量以及响应时间

dstat => 磁盘I/O使用率、IOPS以及吞吐量

iotop => 按I/O大小对进程排序

/proc/slabinfo / slabtop => 目录项、索引节点以及文件系统的缓存

/proc/meminfo => 页缓存和可回收Slab缓存

/proc/diskstats => 磁盘的IOPS、吞吐量以及延迟

/proc/<pid>/io => 进程IOPS、I/O 大小以及I/O延迟

vmstat => 缓存和缓冲区用量汇总

blktrace => 跟踪块设备I/O事件

biosnoop => 跟踪进程的块设备I/O大小

biotop => 跟踪进程块1/O并按I/O大小排序

strace => 跟踪进程的I/O系统调用

perf => 跟踪内核中的/O事件

df => 磁盘空间和索引节点使用量和剩余量

mount => 文件系统的挂载路径以及挂载参数
du => 目录占用的磁盘空间大小

tune2fs => 显示和设置文件系统参数

hdparam => 显示和设置磁盘参数

如何迅速分析 I/O 的性能瓶颈

想弄清楚性能指标的关联性,就要通晓每种性能指标的工作原理

常见的几种排查思路:
1)top => iostat => pidstat => strace + lsof => 结合应用的原理

I/O 基准测试

为了更客观合理地评估优化效果,首先应该对磁盘和文件系统进行基准测试,得到文件系统或者磁盘 I/O 的极限性能。

fio(Flexible I/O Tester)正是最常用的文件系统和磁盘 I/O 性能基准测试工具

# 随机读
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


direct,表示是否跳过系统缓存。上面示例中设置 1 表示跳过系统缓存。

iodepth,表示使用异步 I/O(asynchronous I/O,简称 AIO)时,同时发出的 I/O 请求上限。在上面的示例中,设置的是 64。

rw,表示 I/O 模式。示例 read/write 分别表示顺序读/写,而 randread/randwrite 则分别表示随机读/写。

ioengine,表示 I/O 引擎,它支持同步(sync)、异步(libaio)、内存映射(mmap)、网络(net)等各种 I/O 引擎。上面示例中,设置的 libaio 表示使用异步 I/O。

bs,表示 I/O 的大小。示例中设置成了 4K(这也是默认值)。

filename,表示文件路径,当然,它可以是磁盘路径(测试磁盘性能),也可以是文件路径(测试文件系统性能)。示例中,把它设置成了磁盘 /dev/sdb。注意,用磁盘路径测试写会破坏这个磁盘中的文件系统,所以在使用前必须做好数据备份。

下面就是使用 fio 测试顺序读的一个报告示例:

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 (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 (  KiB/s): min= 8208, max=18832, per=99.85%, avg=17005.35, stdev=998.94, samples=123
   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%

重点关注 slat、clat、lat、bw、iops 指标

slat、clat、lat 都是指 I/O 延迟(latency),但是:
1)slat ,是指从 I/O 提交到实际执行 I/O 的时长(Submission latency);
2)clat ,是指从 I/O 提交到 I/O 完成的时长(Completion latency);
3)lat ,指的是从 fio 创建 I/O 到 I/O 完成的总时长;

注意,对同步 I/O 来说,由于 I/O 提交和 I/O 完成是一个动作,所以 slat 实际上就是 I/O 完成的时间,而 clat 是 0。而从示例可以看到,使用异步 I/O(libaio)时,lat 近似等于 slat + clat 之和。

bw ,它代表吞吐量。在示例中,可以看到,平均吞吐量大约是 16 MB(17005 KiB/1024)

iops ,其实就是每秒 I/O 的次数,上面示例中的平均 IOPS 为 4250

精确模拟应用程序的 I/O 模式

通常情况下,应用程序的 I/O 都是读写并行的,而且每次的 I/O 大小也不一定相同。

如果要真实模拟应用程序的 IO 模式,可以使用 fio 的 I/O 的重放:
1)先用 blktrace ,记录磁盘设备的 I/O 访问情况
2)然后使用 fio ,重放 blktrace 的记录

# 使用blktrace跟踪磁盘I/O,注意指定应用程序正在操作的磁盘
$ blktrace /dev/sdb

# 查看blktrace记录的结果
# ls
sdb.blktrace.0  sdb.blktrace.1

# 将结果转化为二进制文件
$ blkparse sdb -d sdb.bin

# 使用fio重放日志
$ fio --name=replay --filename=/dev/sdb --direct=1 --read_iolog=sdb.bin

I/O 性能优化(在得到 I/O 基准测试报告的基础上)

优化的层次:应用程序优化 > 系统调用 > VFS > FS > Buffer >>>

应用程序优化

1)可以用追加写代替随机写,减少寻址开销,加快 I/O 写的速度
2)可以借助缓存 I/O ,充分利用系统缓存,降低实际 I/O 的次数
3)可以在应用程序内部构建自己的缓存,或者用 Redis 这类外部缓存系统。而操作系统内部缓存是共用的,应用之间会相互影响
4)在需要频繁读写同一块磁盘空间时,可以用 mmap 代替 read/write,减少内存的拷贝次数。
5)在需要同步写的场景中,尽量将写请求合并,而不是让每个请求都同步写入磁盘,即可以用 fsync() 取代 O_SYNC
6)在多个应用程序共享相同磁盘时,为了保证 I/O 不被某个应用完全占用,推荐使用 cgroups 的 I/O 子系统,来限制进程 / 进程组的 IOPS 以及吞吐量
7)在使用 CFQ 调度器时,可以用 ionice 来调整进程的 I/O 调度优先级,特别是提高核心应用的 I/O 优先级。ionice 支持三个优先级类:Idle、Best-effort 和 Realtime。其中, Best-effort 和 Realtime 还分别支持 0-7 的级别,数值越小,则表示优先级别越高

文件系统优化

1)选择最适合的文件系统,相比于 ext4 ,xfs 支持更大的磁盘分区和更大的文件数量,如 xfs 支持大于 16TB 的磁盘。但是 xfs 文件系统的缺点在于无法收缩,而 ext4 则可以。
2)选好文件系统后,还可以进一步优化文件系统的配置选项,包括文件系统的特性(如 ext_attr、dir_index)、日志模式(如 journal、ordered、writeback)、挂载选项(如 noatime)等等。比如, 使用 tune2fs 这个工具,可以调整文件系统的特性
3)可以优化文件系统的缓存。可以优化 pdflush 脏页的刷新频率(比如设置 dirty_expire_centisecs 和 dirty_writeback_centisecs)以及脏页的限额(比如调整 dirty_background_ratio 和 dirty_ratio 等),还可以优化内核回收目录项缓存和索引节点缓存的倾向,即调整 vfs_cache_pressure(/proc/sys/vm/vfs_cache_pressure,默认值 100),数值越大,就表示越容易回收。
4)在不需要持久化时,你还可以用内存文件系统 tmpfs,以获得更好的 I/O 性能

磁盘优化

1)最简单有效的优化方法,就是换用性能更好的磁盘,比如用 SSD 替代 HDD。
2)可以使用 RAID ,把多块磁盘组合成一个逻辑磁盘,构成冗余独立磁盘阵列。这样做既可以提高数据的可靠性,又可以提升数据的访问性能。
3)针对磁盘和应用程序 I/O 模式的特征,选择最适合的 I/O 调度算法。比如,SSD 和 VM 中的磁盘,通常使用 noop 调度算法。数据库应用,推荐使用 deadline 算法。
4)我们可以对应用程序的数据,进行磁盘级别的隔离。比如,我们可以为日志、数据库等 I/O 压力比较重的应用,配置单独的磁盘
5)在顺序读比较多的场景中,我们可以增大磁盘的预读数据。比如调整 /dev/sdb 的预读大小:调整内核选项 /sys/block/sdb/queue/read_ahead_kb,默认大小是 128 KB,单位为 KB;或者使用 blockdev 工具设置,比如 blockdev –setra 8192 /dev/sdb,注意这里的单位是 512B(0.5KB),所以它的数值总是 read_ahead_kb 的两倍
6)可以优化内核块设备 I/O 的选项。比如,调整磁盘队列的长度 /sys/block/sdb/queue/nr_requests,适当增大队列长度,可以提升磁盘的吞吐量(当然也会导致 I/O 延迟增大)

注意,磁盘本身出现硬件错误,也会导致 I/O 性能急剧下降,所以发现磁盘性能急剧下降时,你还需要确认,磁盘本身是不是出现了硬件错误。可以查看 dmesg 中是否有硬件 I/O 故障的日志。 还可以使用 badblocks、smartctl 等工具,检测磁盘的硬件问题,或用 e2fsck 等来检测文件系统的错误。如果发现问题,你可以使用 fsck 等工具来修复。

参考文献

30 | 套路篇:如何迅速分析出系统I/O的瓶颈在哪里?
31 | 套路篇:磁盘 I/O 性能优化的几个思路