简介
一般我们测试硬盘或者存储的性能的时候,会用Linux系统自带的dd命令,因为是自带命令,简单易使用,因此一些客户喜欢使用dd命令来测试磁盘的读写性能。
但是用dd命令来测试性能,有如下问题:
1. dd命令的IO模型单一,只能测试顺序IO,不能测试随机IO。
2. dd命令可设置的参数较少,并且测试结果不一定能反映出磁盘的真实性能。
3. dd命令的设计初衷就不是用例测试性能的,在dd的手册中可以看到。
4. 无法设置队列深度,因此不推荐用dd命令来测试最大读IOPS。
因此,我们要使用更专业的磁盘性能测试工具来测试,目前主流的第三方IO测试工具有fio、iometer和Orion。
iometer 是Windows下使用方便,Orion是Oracle的IO测试软件,而FIO是我们比较常用的在Linux系统下(当然在Windows下安装cygwin也可以运行FIO)的IO系统Benchmark和压力测试工具,可以模拟不同IO场景下的IO负载。
FIO支持 13种不同的 I/O 引擎, 包括:sync,mmap, libaio, posixaio, SG v3, splice, null, network,syslet,guasi,solarisaio 等等。
根据实际业务的场景,一般将 I/O 的表现分为四种场景,随机读、随机写、顺序读、顺序写。FIO 工具允许指定具体的应用模式,配合多线程对磁盘进行不同深度的测试。
安装
FIO默认已经存在于yum仓库里,所以我们yum安装即可
yum install -y libaio-devel
yum install -y fio-3.7-2.el7.x86_64
运行格式
fio [options] [job options] <job file(s)>
工作原理
使用 FIO 工具测试硬盘性能,首先确定待模拟IO负载的IO参数,如I/O type,Block size,I/O engine,I/O depth,Target file/device等
然后需要编写一个作业(命令行或者脚本job file)来预定义 FIO 将要以什么样的模式来执行任务。
作业中可以包含任意数量的线程或文件,一个作业的典型模式是定义一部分的全局共享参数global 段和一个(或多个)作业的job 段实体对象(即硬盘)。
运行 FIO 作业时,FIO 会自上而下解析作业中的配置,并做相应的调整去执行任务。
测试完成后,输出测试结果,对FIO测试结果进行分析,评估IO系统的设计或优化方案或确定debug思路。
FIO 中的常用选项如下
filename: 支持文件系统或者裸设备(硬盘或分区),-filename=/dev/sda2 或 -filename=/tmp/fio_read_test.txt 或 -filename=/dev/sda
direct: 1表示测试过程绕过机器自带的buffer,相当于o_direct,0表示使用bufferio
runtime:测试时长,单位秒,如果不写则直接写5GB文件
time_based: 如果在runtime指定的时间还没到时文件就被读写完成,将继续重复直到runtime时间结束,加上这个参数防止job提前结束。
size: 向硬盘中写入/读取测试文件的大小,可以是绝对值MB,KB,GB,也可以是百分比,占所测磁盘的百分比%,
group_reporting:汇总所有的信息,而不是每个job都显示具体的结果
thread:fio默认会使用fork()创建job进程,如果这个选项设置的话,fio将使用pthread_create来创建线程
numjobs:创建的线程或进程数量,建议等于 CPU 的 core 值,,如果要测试吞吐量,那么numjobs要设置小一点
rw: 读写方式,顺序读read,顺序写write,随机读randread ,随机写randwrite,混合读写randrw,如果要测试吞吐量,需要设为顺序读read,顺序写write
rwmixread:70,混合读写7:3 ,配合rw = randrw
rwmixwrite:30 在混合读写的模式下7:3
bs: 单次io的块大小,一般测试4k, 8k, 64k, 128k, 1M
bsrange: =512-2048 同上,提定数据块的大小范围
allow_mounted_write:允许虚拟机写入
ioengine:FIO 工作时使用的引擎。引擎决定了使用什么样的 I/O,同步模式、异步模式,如果要使用libaio引擎,需要yum install -y libaio-devel包,要使用异步模式引擎才能设置iodepth
iodepth:I/O 引擎如果使用异步模式,要保持多少的队列深度,没有设置则使用操作系统默认队列深度,如果要测试吞吐量,那么iodepth要设置小一点
nrfiles:每个进程/线程生成文件的数量,表示负载将分发到几个文件中。
使用测试
编写 FIO 任务文件
要执行一个 FIO 任务前,需要先定义一个 FIO 任务作业。
按参数输入方式分为如下两种作业定义方式:
(1) 命令行方式(命令行方式一次测试一个磁盘):如fio –filename=/dev/sdb –direct=1 –rw=randread –bs=4k –size=60G –numjobs=64 –runtime=10 –group_reporting –name=test
(2) fio job_file 方式(job_file方式一次测试多个磁盘,每个磁盘写在不同的job段,官方推荐这种方式):如job_file 的名字为 test,则运行方式为 fio test。
按FIO工作模式分为如下两种:
(1)client/server方式:FIO工作模式可以分为前端和后端,即client端和server端。server端执行fio --server,client端执行fio --client=hostname jobfile,这样client端就可以把jobfile发到server端来执行,hostname为server端的IP。
(2)本地执行:命令行 job file。
FIO 任务脚本模板,如果filename写在global段其他job段不写filename,则跟命令行一样,一次只测试一个磁盘
cat fio.conf
[global]
ioengine=libaio
iodepth=4
direct=1
runtime=60
time_based=1
size=1G
group_reporting=1
thread=1
numjobs=2
bs=4k
name='test'
allow_mounted_write=1
nrfiles=1
#filename=/data/total.txt
[seqread]
rw=read
filename=/data/fio_read_test.txt
[seqwrite]
rw=write
filename=/data/fio_write_test.txt
上面的job_file文件转换为命令行形式就是下面的样子
#read 顺序读
fio -ioengine=libaio -direct=1 -bs=4k -nrfiles=1 -thread -rw=read -size=1G -filename=/data/fio_read_test.txt -name='test' -iodepth=4 -runtime=30 -numjobs=2 -time_based=1 -allow_mounted_write=1 -group_reporting
#write 顺序写
fio -ioengine=libaio -direct=1 -bs=4k -nrfiles=1 -thread -rw=write -size=1G -filename=/data/fio_write_test.txt -name='test' -iodepth=4 -runtime=30 -numjobs=2 -time_based=1 -allow_mounted_write=1 -group_reporting
测试结果解读
--------------------------------------------------------------------
展示测试的基本信息
fio fio.conf
seqread: (g=0): rw=read, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=4
...
seqwrite: (g=0): rw=write, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=4
...
fio-3.7
Starting 4 threads
--------------------------------------------------------------------
总共4个job, seqread是2线程,所以是2,seqwrite是2线程,所以是2,加起来4
Jobs: 4 (f=4): [R(2),W(2)][100.0%][r=89.3MiB/s,w=188KiB/s][r=22.9k,w=47 IOPS][eta 00m:00s]
--------------------------------------------------------------------
重点看这里
IOPS 最大IOPS
BW 最大带宽
延时时间
slat表示磁盘需要多久将io提交到kernel做处理
clat表示命令提交到了内核,提交到内核到io完成的时间。
Lat表示从io结果提创建到clat完成,一般说时延,就是看这个值。
clat各个延时所占的百分比。
seqread: (groupid=0, jobs=4): err= 0: pid=11385: Thu Sep 22 17:09:31 2022
read: IOPS=20.5k, BW=80.0MiB/s (83.9MB/s)(4803MiB/60015msec)
slat (nsec): min=1, max=163324k, avg=53835.94, stdev=297380.37
clat (usec): min=50, max=1179.2k, avg=330.98, stdev=4855.59
lat (usec): min=99, max=1179.2k, avg=386.15, stdev=4864.96
clat percentiles (usec):
| 1.00th=[ 113], 5.00th=[ 116], 10.00th=[ 119], 20.00th=[ 123],
| 30.00th=[ 127], 40.00th=[ 131], 50.00th=[ 137], 60.00th=[ 141],
| 70.00th=[ 153], 80.00th=[ 314], 90.00th=[ 367], 95.00th=[ 388],
| 99.00th=[ 490], 99.50th=[ 898], 99.90th=[ 58983], 99.95th=[ 90702],
| 99.99th=[160433]
bw ( KiB/s): min= 7, max=85555, per=50.24%, avg=41180.02, stdev=16441.93, samples=238
iops : min= 1, max=21388, avg=10294.83, stdev=4110.45, samples=238
--------------------------------------------------------------------
同上
write: IOPS=41, BW=165KiB/s (169kB/s)(9952KiB/60190msec)
slat (usec): min=13, max=42741, avg=118.82, stdev=1177.06
clat (msec): min=2, max=1708, avg=193.14, stdev=118.53
lat (msec): min=2, max=1708, avg=193.26, stdev=118.48
clat percentiles (msec):
| 1.00th=[ 33], 5.00th=[ 77], 10.00th=[ 97], 20.00th=[ 120],
| 30.00th=[ 142], 40.00th=[ 161], 50.00th=[ 182], 60.00th=[ 205],
| 70.00th=[ 228], 80.00th=[ 255], 90.00th=[ 288], 95.00th=[ 321],
| 99.00th=[ 472], 99.50th=[ 726], 99.90th=[ 1703], 99.95th=[ 1703],
| 99.99th=[ 1703]
bw ( KiB/s): min= 7, max= 230, per=50.76%, avg=83.76, stdev=35.04, samples=235
iops : min= 1, max= 57, avg=20.55, stdev= 8.71, samples=235
--------------------------------------------------------------------
lat指的是时延,50=0.06%,表示有0.06%的io时延小于50ms。
lat (usec) : 100=0.08%, 250=77.12%, 500=21.64%, 750=0.40%, 1000=0.09%
lat (msec) : 2=0.09%, 4=0.09%, 10=0.11%, 20=0.03%, 50=0.05%
lat (msec) : 100=0.09%, 250=0.17%, 500=0.04%, 750=0.01%, 1000=0.01%
--------------------------------------------------------------------
usr表示用户cpu占用率,sys表示系统cpu占用率,ctx为上下文切换次数,majf是主要页面错误数量, minf是次要页面错误数量
cpu : usr=3.85%, sys=27.45%, ctx=20221, majf=0, minf=18
--------------------------------------------------------------------
队列深度,Submit和complete表示同一时段fio发送和完成的io数据量。Issued为发送的io数量,latency用于调节吞吐量直到达到预设的延迟目标。
IO depths : 1=0.1%, 2=0.1%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.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.0%, >=64=0.0%
issued rwts: total=1229695,2488,0,0 short=0,0,0,0 dropped=0,0,0,0
latency : target=0, window=0, percentile=100.00%, depth=4
--------------------------------------------------------------------
最后是结果的汇总
util:磁盘利用率
Run status group 0 (all jobs):
READ: bw=80.0MiB/s (83.9MB/s), 80.0MiB/s-80.0MiB/s (83.9MB/s-83.9MB/s), io=4803MiB (5037MB), run=60015-60015msec
WRITE: bw=165KiB/s (169kB/s), 165KiB/s-165KiB/s (169kB/s-169kB/s), io=9952KiB (10.2MB), run=60190-60190msec
Disk stats (read/write):
sdb: ios=1229695/2526, merge=0/8, ticks=287074/481664, in_queue=767911, util=100.00%
--------------------------------------------------------------------
关于IO队列深度
在某个时刻,有N个inflight的IO请求,也就是缓冲的IO请求,包括在队列中的IO请求,而N就是队列深度。
加大硬盘队列深度就是让硬盘不断工作,减少硬盘的空闲时间。
加大队列深度 -> 提高IO利用率 -> 获得更高的IOPS和MBPS峰值 ,但是队列深度增加了,IO在队列的等待时间也会增加,导致IO响应时间变大,这个是值得考虑的问题。
就好比一部电梯一次只能搭乘一人,那么每个人一但乘上电梯,就能快速达到目的楼层(单个IO响应时间),但其他人需要耗费较长的等待时间(总IO响应时间)
如果增加电梯容量(队列深度)一次搭乘五个人,那么低楼层的人能快速到达目的楼层(单个IO响应时间),高楼层的人需要耗费多一点点时间(总IO响应时间)
为何要对磁盘I/O进行队列化处理呢?主要目的是提升应用程序的性能。这一点对于多物理磁盘组成的虚拟磁盘(或LUN)显得尤为重要。
如果一次提交一个I/O,虽然响应时间较短,但系统的吞吐量很小。相比较而言,一次提交多个I/O既缩短了磁头移动距离(通过电梯算法),同时也能够提升IOPS。
因此一次向磁盘系统提交多个I/O能够平衡吞吐量和整体响应时间。
在Centos7下下查看系统默认的队列深度
lsscsi -l
[1:0:0:0] cd/dvd NECVMWar VMware IDE CDR10 1.00 /dev/sr0
state=running queue_depth=1 scsi_level=6 type=5 device_blocked=0 timeout=30
[2:0:0:0] disk VMware Virtual disk 1.0 /dev/sda
state=running queue_depth=32 scsi_level=3 type=0 device_blocked=0 timeout=180
[2:0:1:0] disk VMware Virtual disk 1.0 /dev/sdb
state=running queue_depth=32 scsi_level=3 type=0 device_blocked=0 timeout=180
FIO 任务命令行模板,filename需要指定被测磁盘所在分区路径
#read 顺序读 吞吐量
fio -ioengine=libaio -direct=1 -bs=4k -thread -rw=read -size=10G -nrfiles=1 -filename=fio_readputth_test.txt -name='fio read test' -iodepth=2 -runtime=120 -numjobs=4 -time_based=1 -allow_mounted_write=1 -group_reporting
#write 顺序写 吞吐量
fio -ioengine=libaio -direct=1 -bs=4k -thread -rw=write -size=10G -nrfiles=1 -filename=fio_writeputth_test.txt -name='fio write test' -iodepth=2 -runtime=120 -numjobs=4 -time_based=1 -allow_mounted_write=1 -group_reporting
#read 顺序读
fio -ioengine=libaio -direct=1 -bs=4k -thread -rw=read -size=2G -nrfiles=1 -filename=fio_read_test.txt -name='fio read test' -iodepth=4 -runtime=60 -numjobs=8 -time_based=1 -allow_mounted_write=1 -group_reporting
#write 顺序写
fio -ioengine=libaio -direct=1 -bs=4k -thread -rw=write -size=2G -nrfiles=1 -filename=fio_write_test.txt -name='fio write test' -iodepth=4 -runtime=60 -numjobs=8 -time_based=1 -allow_mounted_write=1 -group_reporting
#readwrite 顺序混合读写
fio -ioengine=libaio -direct=1 -bs=4k -thread -rw=readwrite -size=2G -nrfiles=1 -filename=fio_readwrite_test.txt -name='fio readwrite test' -iodepth=4 -runtime=60 -numjobs=8 -time_based=1 -allow_mounted_write=1 -group_reporting
#randread 随机读
fio -ioengine=libaio -direct=1 -bs=4k -thread -rw=randread -size=2G -nrfiles=1 -filename=fio_randread_test.txt -name='fio randread test' -iodepth=4 -runtime=60 -numjobs=8 -time_based=1 -allow_mounted_write=1 -group_reporting
#randwrite 随机写
fio -ioengine=libaio -direct=1 -bs=4k -thread -rw=randwrite -size=2G -nrfiles=1 -filename=fio_randwrite_test.txt -name='fio randwrite test' -iodepth=4 -runtime=60 -numjobs=8 -time_based=1 -allow_mounted_write=1 -group_reporting
#randrw 随机混合读写
fio -ioengine=libaio -direct=1 -bs=4k -thread -rw=randrw -size=2G -nrfiles=1 -filename=fio_randrw_test.txt -name='fio randrw test' -iodepth=4 -runtime=60 -numjobs=8 -time_based=1 -allow_mounted_write=1 -group_reporting
最后,我们可以把结果汇总到表格,方便把报告递给领导