Linux性能分析 – 磁盘篇

一、文件系统工作原理

(1)索引节点和目录项

Linux 文件系统为每个文件都分配两个数据结构,索引节点(index node)和目录项(directory entry)。它们主要用来记录文件的元信息和目录结构。

  • 索引节点,简称为 inode,用来记录文件的元数据,比如 inode 编号、文件大小、访问权限、修改日期、数据的位置等。索引节点和文件一一对应,它跟文件内容一样,都会被持久化存储到磁盘中。所以记住,索引节点同样占用磁盘空间。
  • 目录项,简称为 dentry,用来记录文件的名字、索引节点指针以及与其他目录项的关联关系。多个关联的目录项,就构成了文件系统的目录结构。不过,不同于索引节点,目录项是由内核维护的一个内存数据结构,所以通常也被叫做目录项缓存。

索引节点是每个文件的唯一标志,而目录项维护的正是文件系统的树状结构。目录项和索引节点的关系是多对一,你可以简单理解为,一个文件可以有多个别名。

第一,目录项本身就是一个内存缓存,而索引节点则是存储在磁盘中的数据。
第二,磁盘在执行文件系统格式化时,会被分成三个存储区域,超级块、索引节点区和数据块区。

  • 超级块,存储整个文件系统的状态。
  • 索引节点区,用来存储索引节点。
  • 数据块区,则用来存储文件数据。

(2)虚拟文件系统

目录项、索引节点、逻辑块以及超级块,构成了 Linux 文件系统的四大基本要素。
为了支持各种不同的文件系统,Linux 内核在用户进程和文件系统的中间,又引入了一个抽象层,也就是虚拟文件系统 VFS(Virtual File System)。VFS 定义了一组所有文件系统都支持的数据结构和标准接口。

第一类是基于磁盘的文件系统,常见的 Ext4、XFS、OverlayFS 等,都是这类文件系统。
第二类是基于内存的文件系统,也就是我们常说的虚拟文件系统,/proc 文件系统、/sys 文件系统
第三类是网络文件系统,比如 NFS、SMB、iSCSI 等。

(3)文件系统I/O

最常见的有,缓冲与非缓冲 I/O、直接与非直接 I/O、阻塞与非阻塞 I/O、同步与异步 I/O 等

第一种,根据是否利用标准库缓存,可以把文件 I/O 分为缓冲 I/O 与非缓冲 I/O。

  • 缓冲 I/O,是指利用标准库缓存来加速文件的访问,而标准库内部再通过系统调度访问文件。
  • 非缓冲 I/O,是指直接通过系统调用来访问文件,不再经过标准库缓存。

第二,根据是否利用操作系统的页缓存,可以把文件 I/O 分为直接 I/O 与非直接 I/O。

  • 直接 I/O,是指跳过操作系统的页缓存,直接跟文件系统交互来访问文件。
  • 非直接 I/O 正好相反,文件读写时,先要经过系统的页缓存,然后再由内核或额外的系统调用,真正写入磁盘。

第三,根据应用程序是否阻塞自身运行,可以把文件 I/O 分为阻塞 I/O 和非阻塞 I/O:

  • 阻塞 I/O,是指应用程序执行 I/O 操作后,如果没有获得响应,就会阻塞当前线程,自然就不能执行其他任务。
  • 非阻塞 I/O,是指应用程序执行 I/O 操作后,不会阻塞当前的线程,可以继续执行其他的任务,随后再通过轮询或者事件通知的形式,获取调用的结果。

第四,根据是否等待响应结果,可以把文件 I/O 分为同步和异步 I/O:

  • 同步 I/O,是指应用程序执行 I/O 操作后,要一直等到整个 I/O 完成后,才能获得I/O 响应。
  • 异步 I/O,是指应用程序执行 I/O 操作后,不用等待完成和完成后的响应,而是继续执行就可以。等到这次 I/O 完成后,响应会用事件通知的方式,告诉应用程序。

二、Linux磁盘I/O工作原理

(1)磁盘

磁盘可以分为两类:机械磁盘和固态磁盘。
机械磁盘的最小读写单位是扇区,一般大小为 512 字节。
而固态磁盘的最小读写单位是页,通常大小是 4KB、8KB 等。

硬盘分为 IDE(Integrated Drive Electronics)、SCSI(Small Computer SystemInterface) 、SAS(Serial Attached SCSI) 、SATA(Serial ATA) 、FC(FibreChannel) 等。

另一个比较常用的架构,是把多块磁盘组合成一个逻辑磁盘,构成冗余独立磁盘阵列,也就是 RAID(Redundant Array of Independent Disks)

最后一种架构,是把这些磁盘组合成一个网络存储集群,再通过 NFS、SMB、iSCSI 等网
络存储协议

在 Linux 中,磁盘实际上是作为一个块设备来管理的,也就是以块为单位读写数据,并且支持随机读写。每个块设备都会被赋予两个设备号,分别是主、次设备号。主设备号用在驱动程序中,用来区分设备类型;而次设备号则是用来给多个同类设备编号。

(2)通用块层

通用块层,其实是处在文件系统和磁盘驱动中间的一个块设备抽象层。它主要有两个功能:

  • 第一个功能跟虚拟文件系统的功能类似,
  • 第二个功能,通用块层还会给文件系统和应用程序发来的 I/O 请求排队,并通过重新排序、请求合并等方式,提高磁盘读写的效率。Linux 内核支持四种 I/O 调度算法,分别是 NONE、NOOP、CFQ 以及 DeadLine。
    • 【1】NINE:它完全不使用任何 I/O 调度器,对文件系统和应用程序的 I/O 其实不做任何处理,常用在虚拟机中(此时磁盘 I/O 调度完全由物理机负责)
    • 【2】NOOP:是一个先入先出的队列,只做一些最基本的请求合并,常用于 SSD 磁盘。
    • 【3】CFQ:完全公平调度器,是现在很多发行版的默认 I/O 调度器,它为每个进程维护了一个 I/O 调度队列,并按照时间片来均匀分布每个进程的 I/O 请求。类似于进程 CPU 调度,CFQ 还支持进程 I/O 的优先级调度,所以它适用于运行大量进程的系统,像是桌面环境、多媒体应用等。
    • 【4】DeadLine:分别为读、写请求创建了不同的 I/O 队列,可以提高机械磁盘的吞吐量,并确保达到最终期限(deadline)的请求被优先处理。DeadLine 调度算法,多用在 I/O 压力比较重的场景,比如数据库等。

(3)I/O栈

文件系统层,包括虚拟文件系统和其他各种文件系统的具体实现。它为上层的应用程序,提供标准的文件访问接口;对下会通过通用块层,来存储和管理磁盘数据。

通用块层,包括块设备 I/O 队列和 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 请求从发出到收到响应的间隔时间。

(5)磁盘I/O观测

使用iostat查看I/O性能提供了每个磁盘的使用率、IOPS、吞吐量等各种常见的性能指标


注意:

  • %util ,就是我们前面提到的磁盘 I/O 使用率;
  • r/s+ w/s ,就是 IOPS;
  • rkB/s+wkB/s ,就是吞吐量;
  • r_await+w_await ,就是响应时间。
# -d 表示显示 I/O 性能指标,-x 表示显示扩展统计(即所有 I/O 指标)
root@pan:/tmp# iostat -x -d 1
Linux 4.18.0-15-generic (pan)   2019年08月21日     _x86_64_    (2 CPU)

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
loop2            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
loop3            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
loop4            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
loop5            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
loop6            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
loop7            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  337.00      0.00 344832.00     0.00     1.00   0.00   0.30    0.00  418.93 141.79     0.00  1023.24   2.97 100.00

使用pidstat可以实时查看每个进程的 I/O 情况

  • 用户 ID(UID)和进程 ID(PID) 。
  • 每秒读取的数据大小(kB_rd/s) ,单位是 KB。
  • 每秒发出的写请求数据大小(kB_wr/s) ,单位是 KB。
  • 每秒取消的写请求数据大小(kB_ccwr/s) ,单位是 KB。
  • 块 I/O 延迟(iodelay),包括等待同步块 I/O 和换入块 I/O 结束的时间,单位是时钟周期。
使用 pidstat 加上 -d 参数,就可以显示每个进程的 I/O 情况。
root@pan:/tmp# pidstat -d 1
Linux 4.18.0-15-generic (pan)   2019年08月21日     _x86_64_    (2 CPU)

15时11分18秒   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
15时11分19秒     0       293      0.00     99.01      0.00      44  jbd2/sda1-8
15时11分19秒     0     52233      0.00      0.00      0.00      76  kworker/u256:2+flush-8:0
15时11分19秒     0     52353      0.00 312930.69      0.00      20  python

  # 在终端中运行 strace 命令,并通过 -p 52353 指定 python 进程的 PID 号
    root@pan:/tmp# strace -p 52353
    strace: Process 52353 attached
    munmap(0x7fd6f5680000, 314576896)       = 0
    write(3, "\n", 1)                       = 1
    munmap(0x7fd708281000, 314576896)       = 0
    select(0, NULL, NULL, NULL, {tv_sec=0, tv_usec=100000}) = 0 (Timeout)
    getpid()                                = 1
    mmap(NULL, 314576896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd708281000
    mmap(NULL, 393220096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd6f0b80000
    mremap(0x7fd6f0b80000, 393220096, 314576896, MREMAP_MAYMOVE) = 0x7fd6f0b80000
    munmap(0x7fd708281000, 314576896)       = 0
    lseek(3, 0, SEEK_END)                   = 943718535
    lseek(3, 0, SEEK_CUR)                   = 943718535
    munmap(0x7fd6f0b80000, 314576896)       = 0
    close(3)                                = 0
    stat("/tmp/logtest.txt.1", {st_mode=S_IFREG|0644, st_size=943718535, ...}) = 0
    unlink("/tmp/logtest.txt.1")            = 0
    stat("/tmp/logtest.txt", {st_mode=S_IFREG|0644, st_size=943718535, ...}) = 0
    rename("/tmp/logtest.txt", "/tmp/logtest.txt.1") = 0
    open("/tmp/logtest.txt", O_WRONLY|O_CREAT|O_APPEND|O_CLOEXEC, 0666) = 3

    # lsof 命令,看看进程 52353 都打开了哪些文件
    root@pan:/tmp# lsof  -p 52353
    lsof: WARNING: can't stat() fuse.gvfsd-fuse file system /run/user/1000/gvfs
          Output information may be incomplete.
    COMMAND   PID USER   FD   TYPE DEVICE  SIZE/OFF    NODE NAME
    python  52353 root  cwd    DIR   0,52      4096 3674817 /
    python  52353 root  rtd    DIR   0,52      4096 3674817 /
    python  52353 root  txt    REG   0,52     28016 3671279 /usr/local/bin/python3.7
    python  52353 root  mem    REG    8,1           3671279 /usr/local/bin/python3.7 (stat: No such file or directory)
    python  52353 root  mem    REG    8,1           3672008 /usr/local/lib/python3.7/lib-dynload/_queue.cpython-37m-x86_64-linux-gnu.so (stat: No such file or directory)
    python  52353 root  mem    REG    8,1           3672006 /usr/local/lib/python3.7/lib-dynload/_pickle.cpython-37m-x86_64-linux-gnu.so (stat: No such file or directory)
    python  52353 root  mem    REG    8,1           3672017 /usr/local/lib/python3.7/lib-dynload/_struct.cpython-37m-x86_64-linux-gnu.so (stat: No such file or directory)
    python  52353 root  mem    REG    8,1           3672039 /usr/local/lib/python3.7/lib-dynload/select.cpython-37m-x86_64-linux-gnu.so (stat: No such file or directory)
    python  52353 root  mem    REG    8,1           3672014 /usr/local/lib/python3.7/lib-dynload/_socket.cpython-37m-x86_64-linux-gnu.so (stat: No such file or directory)
    python  52353 root  mem    REG    8,1           3672009 /usr/local/lib/python3.7/lib-dynload/_random.cpython-37m-x86_64-linux-gnu.so (stat: No such file or directory)
    python  52353 root  mem    REG    8,1           3671976 /usr/local/lib/python3.7/lib-dynload/_bisect.cpython-37m-x86_64-linux-gnu.so (stat: No such file or directory)
    python  52353 root  mem    REG    8,1           3672012 /usr/local/lib/python3.7/lib-dynload/_sha3.cpython-37m-x86_64-linux-gnu.so (stat: No such file or directory)
    python  52353 root  mem    REG    8,1           3671977 /usr/local/lib/python3.7/lib-dynload/_blake2.cpython-37m-x86_64-linux-gnu.so (stat: No such file or directory)
    python  52353 root  mem    REG    8,1           3670240 /lib/libcrypto.so.43.0.1 (stat: No such file or directory)
    python  52353 root  mem    REG    8,1           3671997 /usr/local/lib/python3.7/lib-dynload/_hashlib.cpython-37m-x86_64-linux-gnu.so (stat: No such file or directory)
    python  52353 root  mem    REG    8,1           3672031 /usr/local/lib/python3.7/lib-dynload/math.cpython-37m-x86_64-linux-gnu.so (stat: No such file or directory)
    python  52353 root  mem    REG    8,1           3671998 /usr/local/lib/python3.7/lib-dynload/_heapq.cpython-37m-x86_64-linux-gnu.so (stat: No such file or directory)
    python  52353 root  mem    REG    8,1           3670182 /etc/localtime (path inode=921411)
    python  52353 root  mem    REG    8,1           3671388 /usr/local/lib/libpython3.7m.so.1.0 (stat: No such file or directory)
    python  52353 root  mem    REG    8,1           3670237 /lib/ld-musl-x86_64.so.1 (stat: No such file or directory)
    python  52353 root    0u   CHR  136,0       0t0       3 /dev/pts/0
    python  52353 root    1u   CHR  136,0       0t0       3 /dev/pts/0
    python  52353 root    2u   CHR  136,0       0t0       3 /dev/pts/0
    python  52353 root    3w   REG    8,1 704102400 1573091 /tmp/logtest.txt

使用iotop工具,此命令类似top

分析套路:

  • 1、用top查看指标,发现 [系统] 有i/o瓶颈 或者 cpu瓶颈.
  • 2、使用iostat辅助看下磁盘i/o读写速度和大小等指标(iostat -d -x 1)
  • 3、用pidstat判断是哪个 [进程] 导致的,(pidstat -d 1)既可以看进程各线程的cpu中断数,也可以看磁盘i/o
  • 4、用strace命令跟踪进程(strace -f -T -tt -p xxxx)调用,重点关注epoll_pwait、read、write、fdatasync这些系统调用
  • 5、再用lsof命令,找出这些系统调用操作对象。

磁盘 I/O 性能指标

  • 使用率,是指磁盘忙处理 I/O 请求的百分比。过高的使用率(比如超过 60%)通常意味着磁盘 I/O 存在性能瓶颈。
  • IOPS(Input/Output Per Second),是指每秒的 I/O 请求数。
  • 吞吐量,是指每秒的 I/O 请求大小。
  • 响应时间,是指从发出 I/O 请求到收到响应的间隔时间。

排查磁盘性能IO问题常用工具:

磁盘性能IO排查图

三、磁盘I/O性能优化的几个思路

(1)I/O基准测试

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

安装

Ubuntu

apt-get install -y fio
# CentOS
yum install -y fio

使用方式:任意位置输入命令:

fio -name=/mnt/vdb/tmpfile -direct=1 -iodepth=1 -rw=randwrite -ioengine=posixaio -bs=16k -size=16G -numjobs=1  -group_reporting=1
  • direct 是(1)否(0)跳过buffer使用直接IO;
  • iodepth IO队列深度;
  • ioengine=posixaio使用posix异步io库posixio作为IO引擎,异步IO;
  • bs 16k单次IO块大小为16K;
  • size 16G总共IO大小;
  • rw指定测试类别此处为randwrite随机写;

命令返回结果重点关注slat、clat、lat 这几行都是之I/O延迟不同之处在与:

  • slat ,是指从 I/O 提交到实际执行 I/O 的时长(Submission latency);
  • clat ,是指从 I/O 提交到 I/O 完成的时长(Completion latency);
  • lat ,指的是从 fio 创建 I/O 到 I/O 完成的总时长(等于 slat + clat 之和)
  • bw ,它代表吞吐量
  • iops ,其实就是每秒 I/O 的次数

fio 支持 I/O 的重放。借助前面提到过的 blktrace,再配合上 fio,就可以实现对应用程序 I/O 模式的基准测试。

# 使用 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

(2)I/O性能优化

1)应用程序优化

  • 第一,可以用追加写代替随机写,减少寻址开销,加快 I/O 写的速度。
  • 第二,可以借助缓存 I/O ,充分利用系统缓存,降低实际 I/O 的次数。
  • 第三,可以在应用程序内部构建自己的缓存,或者用 Redis 这类外部缓存系统。
    • 能在应用程序内部,控制缓存的数据和生命周期
    • 也能降低其他应用程序使用缓存对自身的影响

2)文件系统优化

  • 第一,你可以根据实际负载场景的不同,选择最适合的文件系统
    ext4 ,xfs 支持更大的磁盘分区和更大的文件数量,如 xfs 支持大于 16TB 的磁盘。但是 xfs 文件系统的缺点在于无法收缩,而 ext4 则可以。
  • 第二,在选好文件系统后,还可以进一步优化文件系统的配置选项,包括文件系统的特性(如 ext_attr、dir_index)、日志模式(如 journal、ordered、writeback)、挂载选项(如 noatime)等等。
  • 第三,可以优化文件系统的缓存。可以优化 pdflush 脏页的刷新频率以及脏页的限额,还可以优化内核回收目录项缓存和索引节点缓存的倾向,即调整vfs_cache_pressure数值越大,就表示越容易回收。
  • 第四,需要持久化时,你还可以用内存文件系统 tmpfs,以获得更好的 I/O 性能 。tmpfs 把数据直接保存在内存中,而不是磁盘中

3)磁盘优化

  • 第一,最简单有效的优化方法,就是换用性能更好的磁盘,比如用 SSD 替代 HDD。
  • 第二,我们可以使用 RAID ,把多块磁盘组合成一个逻辑磁盘,构成冗余独立磁盘阵列。
  • 第三,针对磁盘和应用程序 I/O 模式的特征,我们可以选择最适合的 I/O 调度算法。
  • 第四,我们可以对应用程序的数据,进行磁盘级别的隔离。我们可以为日志、数据库等 I/O 压力比较重的应用,配置单独的磁盘
  • 第五,在顺序读比较多的场景中,我们可以增大磁盘的预读数据。
  • 第六,我们可以优化内核块设备 I/O 的选项

磁盘问题检测推荐使用命令工具:
dmesg 中是否有硬件 I/O 故障的日志。 还可以使用 badblocks、smartctl 等工具,检测磁盘的硬件问题,或用 e2fsck 等来检测文件系统的错误。如果发现问题,你可以使用 fsck 等工具来修复。

|| 版权声明
作者:废权
链接:https://blog.yjscloud.com/archives/118
声明:如无特别声明本文即为原创文章仅代表个人观点,版权归《废权的博客》所有,欢迎转载,转载请保留原文链接。
THE END
分享
二维码
Linux性能分析 – 磁盘篇
一、文件系统工作原理 (1)索引节点和目录项 Linux 文件系统为每个文件都分配两个数据结构,索引节点(index node)和目录项(directory entry)。它们主要用……
<<上一篇
下一篇>>
文章目录
关闭
目 录