存储IO落盘原理及路径分析

作者:吴业亮
博客:wuyeliang.blog.csdn.net

一、IO落盘原理

一个I/O请求,从应用层到底层块设备,路径如下图所示:
存储IO落盘原理及路径分析_第1张图片

从上图可以看出IO路径是很复杂的。我们将IO路径简化一下:
存储IO落盘原理及路径分析_第2张图片

一个I/O请求进入block layer之后,可能会经历下面的过程:
• Remap: 可能被DM(Device Mapper)或MD(Multiple Device, Software RAID) remap到其它设备

• Split: 可能会因为I/O请求与扇区边界未对齐、或者size太大而被分拆(split)成多个物理I/O

• Merge: 可能会因为与其它I/O请求的物理位置相邻而合并(merge)成一个I/O

• 被IO Scheduler依照调度策略发送给driver

• 被driver提交给硬件,经过HBA、电缆(光纤、网线等)、交换机(SAN或网络)、最后到达存储设备,设备完成IO请求之后再把结果发回。

根据以上步骤对应的时间戳就可以计算出I/O请求在每个阶段所消耗的时间:

  • Q2Q — time between requests sent to the block layer
  • Q2G – 生成IO请求所消耗的时间,包括remap和split的时间;
  • G2I – IO请求进入IO Scheduler所消耗的时间,包括merge的时间;
  • Q2M — time from a block I/O is queued to the time it gets merged with an existing request
  • I2D – IO请求在IO Scheduler中等待的时间;
  • D2C – IO请求在driver和硬件上所消耗的时间;
  • Q2C – 整个IO请求所消耗的时间(Q2I + I2D + D2C = Q2C),相当于iostat的await。

用一张图来表示:
存储IO落盘原理及路径分析_第3张图片

如果I/O性能慢的话,以上指标有助于进一步定位缓慢发生的地方:

  • D2C可以作为硬件性能的指标;
  • I2D可以作为IO Scheduler性能的指标。

附上event速查表: 存储IO落盘原理及路径分析_第4张图片

注意以下几点:
• Q2Q是表示两个reqeusts之间的间隔;而其他都是表示一个request的各个阶段的间隔。
• 和第2节中的IO的流程图一致,分为两个路径:上面绿色Q->G->I->D表示一个典型request(没有被merge的request)特有的阶段;下面红色为Q->M->D表示一个被merge的request特有的阶段;黄色部分表示公共阶段。
• Q2G一般比较小,若它比较大,说明分配不到struct request,中途经历了S(sleep)阶段。

二、IO路径分析工具blktrace

由于在iostat输出中,只能看到service time + wait time,因为对于评估一个磁盘或者云磁盘而言,service time才是衡量磁盘性能的核心指标和直接指标。很不幸iostat无法提供这个指标,但是blktrace可以。blktrace是一柄神器,很多工具都是基于该神器的:ioprof,seekwatcher,iowatcher,这个工具基本可以满足我们的对块设备请求的所有了解。

1、Blktrace工作原理
blktrace是block层的trace机制,它可以跟踪IO请求的生成、进入队列、发到driver以及完成等事件。blktrace包含3个组件:

• 一个内核模块
• 一个把内核模块跟踪到的IO信息导出到用户态的工具(blktrace命令)
• 一些分析和展示IO信息的工具(blkparse, btt命令)

所以广义的blktrace包含这3个部分,狭义的blktrace只是blktrace命令。本文介绍广义的blktrace:包括使用blktrace命令导出IO信息,然后使用blkparse和btt分析展示。blktrace命令通过 debug file system 从内核导出数据:
• blktrace从kernel接收数据并通过debug file system的buffer传递到用户态;debug file system默认是/sys/kernel/debug,可以使用blktrace命令的-r选项来覆盖。buffer的大小和数量默认分别是512KiB和4,可以通过-b和-n来覆盖。blktrace运行的的时候,可以看见debug file system里有一个block/{device}(默认是/sys/kernel/debug/block/{device})目录被创建出来,里面有一些trace{cpu}文件。

• blktrace默认地搜集所有trace到的事件,可以使用blktrace命令的-a选项来指定事件。

• blktrace把从内核接收到的数据写到当前目录,文件名为{device}.blktrace.{cpu},内容是二进制数据(对于人来说是不可读的;blkparse用于解析这些二进制数据)。例如blktrace -d /dev/sdc会生成sdc.blktrace.0, sdc.blktrace.1, … sdc.blktrace.N-1个文件,N为cpu个数。也可使用-o选项来自定义{device}部分,这方便和blkparse结合使用:blktrace的-o参数对应blkparse的-i参数。

• blktrace默认地会一直运行,直到被ctrl-C停掉。可以使用-w选项来指定运行时间,单位是秒。
blktrace会区分两类请求:

• 文件系统请求(fs requests):通常是用户态进程产生的,读写disk的特定位置和特定大小的数据。当然,也有可能由内核产生:flush脏页或sync文件系统的元数据(super block或journal block等)。

• SCSI命令(pc requests):blktrace直接把SCSI命令发送到用户态,blkparse可以解析它。

2、Blktrace安装
yum安装方式

 yum install blktrace

源码获取(你也可以从源码安装)

git clone git://git.kernel.org/pub/scm/linux/kernel/git/axboe/blktrace.git bt
cd bt
make
make install

可以查看磁盘上的实时信息:

 blktrace -d /dev/sdb -o – | blkparse -i –

这个命令会连绵不绝地出现很多输出,当你输入ctrl+C的时候,会停止。
存储IO落盘原理及路径分析_第5张图片

命令采集信息,待所有信息采集完毕后,统一分析所有采集到的数据。搜集60s信息的命令如下:

blktrace -d /dev/sdb -w 60

存储IO落盘原理及路径分析_第6张图片

首先blkparse可以将对应不同cpu的多个文件聚合成一个文件:

blkparse -i sdb -d sdb.blktrace.bin

利用btt协助分析统计

#btt -i sdb.blktrace.bin -l sdb.d2c_latency

存储IO落盘原理及路径分析_第7张图片

注意:

  • D2C: 表征块设备性能的关键指标
  • Q2C: 客户发起请求到收到响应的时间
  • D2C 平均时间:0.002494923 秒,即2.49毫秒
  • Q2C 平均时间:0.002496605 秒,即2.49毫秒
  • 平均下来,D2C 阶段消耗时间占比 99.9326%
    上述命令其实还会产生一些.dat文件,可以看到iops信息
    存储IO落盘原理及路径分析_第8张图片

文件解释:

sys_mbps_fs.dat: 本次统计中所有设备吞吐量
sys_iops_fp.dat: 中是本次统计中所有设备的IOPS

blktrace 能够记录下IO所经历的各个步骤:
存储IO落盘原理及路径分析_第9张图片

我们一起看下blktrace的输出参数解释:
存储IO落盘原理及路径分析_第10张图片

• 第一个字段:8,0 这个字段是设备号 major device ID和minor device ID。
• 第二个字段:3 表示CPU
• 第三个字段:11 序列号
• 第四个字段:0.009507758 Time Stamp是时间偏移
• 第五个字段:PID 本次IO对应的进程ID
• 第六个字段:Event,这个字段非常重要,反映了IO进行到了那一步
• 第七个字段:R表示 Read, W是Write,D表示block,B表示Barrier Operation
• 第八个字段:223490+56,表示的是起始block number 和 number of blocks,即我们常说的Offset 和 Size
• 第九个字段: 进程名

其中第六个字段非常有用:每一个字母都代表了IO请求所经历的某个阶段。

Q – 即将生成IO请求
G – IO请求生成
I – IO请求进入IO Scheduler队列|
D – IO请求进入driver|
C – IO请求执行完毕

Summary包括CPU和device两个维度:
存储IO落盘原理及路径分析_第11张图片

其中对于每个CPU或者对于Total:
• Writes Queued:trace时间内,Q(queue)的requests个数;

• Writes Requeued:trace时间内,requeue数。requeue可能来自于multi-layer

• Write Dispatches:trace时间内,block layer发送到driver的requests数;

• Write Merges:trace时间内,merge的requests数;

• Writes Completed:trace时间内,完成的requests数;

• Timer unplugs:超时导致的unplug数?

Writes Queued与Writes Requeued之和是trace时间内block layer接收的requests总数(incomming requests)。Write Dispatches和Write Merges之和是trace时间内block layer发出的(到driver)requests数(outgoing requests)。进等于出,所以:

Writes Queued + Writes Requeued = Write Dispatches + Write Merges

Write Dispatches是block layer发送到driver的requests数,但不是所有发送到driver的都完成了,其中有一部分被requeue了,即:

 Write Dispatches = Writes Completed + Writes Requeued 

我们来分析一个io过程,将一块空白的磁盘格式化挂载一个目录,然后再写入一个文件

在这里插入图片描述
存储IO落盘原理及路径分析_第12张图片

以上就是一次IO请求的生命周期,从actions看到,分别是QGPIUDC

  • Q:先产生一个该位置的IO意向插入到io队列,此时并没有真正的请求
  • G:发送一个实际的Io请求给设备
  • P(plugging):插入:即等待即将到来的更多的io请求进入队列,以便系统能进行IO优化,减少执行IO请求时花的时间
  • I:将IO请求进行调度,到这一步请求已经完全成型(formed)好了
  • U (unplugging):拔出,设备决定不再等待其他的IO请求并使得系统必须响应当前IO请求,将该IO请求传给设备驱动器。可以看到,在P和U之间会等待IO,然后进行调度。这里会对IO进行一点优化,
    但是程度很低,因为等待的时间很短,是纳秒级别的
  • D :发布刚才送入驱动器的IO请求
  • C:结束IO请求,这里会返回一个执行状态:失败或者成功,在进程号处为0表示执行成功,反之则反
    到此一个IO的周期就结束了

案例分析:
案例一:
把/dev/sde(设备号:8,64)整个盘格式化成ext4,挂载到/mnt并使用fio写/mnt/fio-test-file (rw=randwrite ioengine=psync bs=4k fsync=1),同时trace /dev/sde

# blktrace -d /dev/sde -w 60
# blkparse -i sde

......

  8,64  11     2944    33.779658276 90790  Q   W 7614344 + 24 [fio]
  8,64  11     2945    33.779659493 90790  G   W 7614344 + 24 [fio]
  8,64  11     2946    33.779660124 90790  P   N [fio]
  8,64  11     2947    33.779661287 90790  I   W 7614344 + 24 [fio]
  8,64  11     2948    33.779661791 90790  U   N [fio] 1
  8,64  11     2949    33.779662409 90790  D   W 7614344 + 24 [fio]
  8,64  11     2950    33.791580458     0  C   W 7614344 + 24 [0]
  8,64  11     2951    33.791608964 90790  Q  WS 7614368 + 8 [fio]
  8,64  11     2952    33.791609940 90790  G  WS 7614368 + 8 [fio]
  8,64  11     2953    33.791610381 90790  P   N [fio]
  8,64  11     2954    33.791611867 90790  I  WS 7614368 + 8 [fio]
  8,64  11     2955    33.791612372 90790  U   N [fio] 1
  8,64  11     2956    33.791612977 90790  D  WS 7614368 + 8 [fio]
  8,64  11     2957    33.799933777     0  C  WS 7614368 + 8 [0]
  8,64  13     5709    33.799963426 90708  Q  WS 3905206216 + 8 [jbd2/sde-8]
  8,64  13     5710    33.799964662 90708  G  WS 3905206216 + 8 [jbd2/sde-8]
  8,64  13     5711    33.799965100 90708  P   N [jbd2/sde-8]
  8,64  13     5712    33.799965942 90708  Q  WS 3905206224 + 8 [jbd2/sde-8]
  8,64  13     5713    33.799966423 90708  M  WS 3905206224 + 8 [jbd2/sde-8]
  8,64  13     5714    33.799967148 90708  Q  WS 3905206232 + 8 [jbd2/sde-8]

......

CPU8 (sde):
 Reads Queued:           0,        0KiB  Writes Queued:         195,   29,228KiB
 Read Dispatches:        0,        0KiB  Write Dispatches:      170,   29,228KiB
 Reads Requeued:         0               Writes Requeued:         0
 Reads Completed:        0,        0KiB  Writes Completed:      170,   29,228KiB
 Read Merges:            0,        0KiB  Write Merges:           25,      100KiB
 Read depth:             0               Write depth:            20
 IO unplugs:           104               Timer unplugs:           0
CPU9 (sde):
 Reads Queued:           0,        0KiB  Writes Queued:         991,   16,156KiB
 Read Dispatches:        0,        0KiB  Write Dispatches:      574,   16,156KiB
 Reads Requeued:         0               Writes Requeued:         0
 Reads Completed:        0,        0KiB  Writes Completed:      574,   16,156KiB
 Read Merges:            0,        0KiB  Write Merges:          417,    1,668KiB
 Read depth:             0               Write depth:            20
 IO unplugs:           346               Timer unplugs:           0
......
Total (sde):
 Reads Queued:           0,        0KiB  Writes Queued:       8,763,  352,192KiB
 Read Dispatches:        0,        0KiB  Write Dispatches:    5,380,  352,192KiB
 Reads Requeued:         0               Writes Requeued:         0
 Reads Completed:        0,        0KiB  Writes Completed:    5,380,  352,196KiB
 Read Merges:            0,        0KiB  Write Merges:        3,383,   13,532KiB
 IO unplugs:         3,092               Timer unplugs:           0

Throughput (R/W): 0KiB/s / 5,872KiB/s
Events (sde): 39,850 entries
Skips: 0 forward (0 -   0.0%)

我们尝试把一个特定request的事件给找出来(grep一个特定的起始sector号,而不是grep一个序列号):

# blkparse -i sde | grep -w 7614368
  8,64  11     2951    33.791608964 90790  Q  WS 7614368 + 8 [fio]
  8,64  11     2952    33.791609940 90790  G  WS 7614368 + 8 [fio]
  8,64  11     2954    33.791611867 90790  I  WS 7614368 + 8 [fio]
  8,64  11     2956    33.791612977 90790  D  WS 7614368 + 8 [fio]
  8,64  11     2957    33.799933777     0  C  WS 7614368 + 8 [0]

这是一个Write+Sync请求,起始sector是7614368,大小是8个secotr(即4k)。它是一个典型的request:经历了Q->G->I->D-C几个阶段,总耗时:33.799933777-33.791608964 = 8324813纳秒 = 8.3毫秒。
Summary部分的Total(sde):

Incomming Requests = Writes Queued + Writes Requeued = 8763 + 0 = 8763
 Outgoing Requests = Write Dispatches + Write Merges = 5380 + 3383 = 8763 

另外,可以看到各个read指标都为0,因为fio全是写操作且大小和文件系统block对齐。可以对比还是全写操作但bs=5k(不再和文件系统block对齐)的情形,这时候read指标就不为0了,因为不对齐,文件系统需要把部分修改的block读出来,修改然后再写回去。

案例二:
把/dev/sde(设备号:8,64)分区并把/dev/sde2(设备号:8,66)格式化成ext4,挂载到/mnt并使用fio写/mnt/fio-test-file (rw=randwrite ioengine=psync bs=4k fsync=1),同时还是trace /dev/sde:

# blktrace -d /dev/sde -w 60
# blkparse -i sde

  8,64  14     1000    21.217022761 96519  D  WS 5857775072 + 8 [jbd2/sde2-8]
  8,64  14     1001    21.225342198     0  C  WS 5857775072 + 8 [0]
  8,64  20     1160    21.225404128 98523  A  WS 3927579616 + 8 <- (8,66) 20560864
  8,64  20     1161    21.225405121 98523  Q  WS 3927579616 + 8 [fio]
  8,64  20     1162    21.225407092 98523  G  WS 3927579616 + 8 [fio]
  8,64  20     1163    21.225407687 98523  P   N [fio]
  8,64  20     1164    21.225409225 98523  I  WS 3927579616 + 8 [fio]
  8,64  20     1165    21.225410004 98523  U   N [fio] 1
  8,64  20     1166    21.225411107 98523  D  WS 3927579616 + 8 [fio]
  8,64   0     1176    21.238455782     0  C  WS 3927579616 + 8 [0]
  8,64  14     1002    21.238482169 96519  A  WS 5857775080 + 8 <- (8,66) 1950756328
  8,64  14     1003    21.238482610 96519  Q  WS 5857775080 + 8 [jbd2/sde2-8]
  8,64  14     1004    21.238483773 96519  G  WS 5857775080 + 8 [jbd2/sde2-8]

......

Total (sde):
 Reads Queued:           0,        0KiB  Writes Queued:       7,558,  339,096KiB
 Read Dispatches:        0,        0KiB  Write Dispatches:    5,707,  339,096KiB
 Reads Requeued:         0               Writes Requeued:         0
 Reads Completed:        0,        0KiB  Writes Completed:    5,707,  339,096KiB
 Read Merges:            0,        0KiB  Write Merges:        1,851,    7,404KiB
 IO unplugs:         3,402               Timer unplugs:           0
# blkparse -i sde | grep -w 3927579616
  8,64  20     1160    21.225404128 98523  A  WS 3927579616 + 8 <- (8,66) 20560864
  8,64  20     1161    21.225405121 98523  Q  WS 3927579616 + 8 [fio]
  8,64  20     1162    21.225407092 98523  G  WS 3927579616 + 8 [fio]
  8,64  20     1164    21.225409225 98523  I  WS 3927579616 + 8 [fio]
  8,64  20     1166    21.225411107 98523  D  WS 3927579616 + 8 [fio]
  8,64   0     1176    21.238455782     0  C  WS 3927579616 + 8 [0]

看起始位置为3927579616的request,和例1中的request相比,这个request多了一个A事件,因为这个IO是从/dev/sde2(设备号:8,66)remap到/dev/sde(设备号:8,64)的。总耗时21.238455782-21.225404128 = 13051654纳秒 = 13.05毫秒。

案例三:
基于例2的数据,我们找一个merge的请求:

# blkparse -i sde | grep -w M
  ......
  8,64  13     4434    59.194332313 96519  M  WS 5857801440 + 8 [jbd2/sde2-8]
# blkparse -i sde | grep -w 5857801440
  8,64  13     4432    59.194331558 96519  A  WS 5857801440 + 8 <- (8,66) 1950782688
  8,64  13     4433    59.194331878 96519  Q  WS 5857801440 + 8 [jbd2/sde2-8]
  8,64  13     4434    59.194332313 96519  M  WS 5857801440 + 8 [jbd2/sde2-8]

这个请求从remap到queued,然后再merge到一个已存在的请求中,之后便追踪不到了:因为它是其他某个请求的一部分,而不是一个独立的请求。假如知道它被merge到哪个请求了,去追踪那个请求,就能知道后续各个阶段。另外注意,被合并的情况下,没有get request (G)阶段,而直接被合并了。
通过这三个例子我们可以看出,blktrace可以追踪到一个特定request的各个阶段,及各个阶段的耗时。但这太详细了,我们无法逐一查看各个request。这时btt命令就派上用场了,它可以生成报表。blkparse还有一个功能:把blktrace生成的{device}.blktrace.{cpu}一堆文件dump成一个二进制文件,输出到-d指定的文件中(忽略标准输出)。这个功能正好方便btt的使用。

# blkparse -i sde -d sde.blktrace.bin
# ls sde.blktrace.bin
sde.blktrace.bin

参考:

https://www.cnblogs.com/xiaotengyi/p/7681898.html
https://developer.aliyun.com/article/698568
https://bean-li.github.io/blktrace-to-report/
https://www.yuanguohuo.com/2019/08/28/linux-blktrace/
http://linuxperf.com/?p=161
https://www.cnblogs.com/cobbliu/p/7002933.html

你可能感兴趣的:(云存储,linux,blktrace,性能,落盘)