Linux 文件系统是怎么工作的?

  • 索引节点&目录项

    • Linux文件系统为每个文件都分配两个数据结构:索引节点(index node)和目录项(directory entry)
    • 索引节点,简称为 inode,用来记录文件的元数据,比如 inode 编号、文件大小、访问权限、修改日期、数据的位置等。索引节点和文件一一对应,它跟文件内容一样,都会被持久化存储到磁盘中。索引节点同样占用磁盘空间。
    • 目录项,简称为 dentry,用来记录文件的名字、索引节点指针以及与其他目录项的关联关系。多个关联的目录项,就构成了文件系统的目录结构。不过,不同于索引节点,目录项是由内核维护的一个内存数据结构,所以通常也被叫做目录项缓存。
    • 目录项本身就是一个内存缓存,而索引节点则是存储在磁盘中的数据,并且也会缓存到内存中加速访问
    • 磁盘被划分为三个存储区域:超级块、索引节点区和数据块区

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

    • 为了支持各种不同的文件系统,Linux 内核在用户进程和文件系统的中间,又引入了一个抽象层,也就是虚拟文件系统 VFS
    • VFS 定义了一组所有文件系统都支持的数据结构和标准接口。这样,用户进程和内核中的其他子系统,只需要跟 VFS 提供的统一接口进行交互,而不需要再关心底层各种文件系统的实现细节
    • 底层文件系统分为三类:基于磁盘、内存、网络的文件系统

      • 基于磁盘的文件系统,即数据直接存储在计算机本地挂载的磁盘中。常见的 Ext4、XFS、OverlayFS 等
      • 基于内存的文件系统,也就是常说的虚拟文件系统。不需要任何磁盘分配存储空间,但会占用内存
      • 网络文件系统,也就是用来访问其他计算机数据的文件系统,比如 NFS、SMB、iSCSI 等
  • 文件系统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 完成后,响应会用事件通知的方式,告诉应用程序。
  • 性能观测工具

    • 容量

      • df。所用命令:df -h(-h用可读性更好的容量单位,-i表示展示索引节点的容量占用情况)
    • 缓存

      • cat /proc/meminfo | grep -E "SReclaimable|Cached"
      • cat /proc/slabinfo | grep -E '^#|dentry|inode'
      • slabtop

Linux 磁盘I/O是怎么工作的

  • 虚拟文件系统(VFS)

    • 目录项,记录了文件的名字,以及文件与其他目录项之间的目录关系(内存缓存)
    • 索引节点,记录了文件的元数据(持久化数据)
    • 逻辑块,是由连续磁盘扇区构成的最小读写单元,用来存储文件数据(持久化数据)
    • 超级块,用来记录文件系统整体的状态,如索引节点和逻辑块的使用情况等(持久化数据)
  • 磁盘

    • 按存储介质分类:机械磁盘(HDD)、固态磁盘(SSD)

      • 机械磁盘

        • 盘片和磁头组成;随机I/O性能慢(要移动磁头)
        • 最小读写单位:扇区(512byte)
      • 固态磁盘

        • 无需寻址,速度快;
        • 最小读写单位:页(通常4KB、8KB)
      • 文件系统会把连续的扇区或页,组成逻辑块,作为最小管理单元
    • 按接口分类:IDE、SCSI 、SAS 、SATA 、FC等

      • 不同接口的设备,会分配不同的前缀作为设备名(例如IDE设备以hd开头,SCSI以sd开头)
      • 相同接口的多块设备,再以字母a、b、c等编号(例如sda,sdb)
      • 同一块设备,又可以分为不同的逻辑分区,以数字区分(sda1,sda2)
    • 在 Linux 中,磁盘实际上是作为一个块设备来管理的,也就是以块为单位读写数据,并且支持随机读写。
    • 每个块设备都会被赋予两个设备号,分别是主、次设备号。主设备号用在驱动程序中,用来区分设备类型;而次设备号则是用来给多个同类设备编号。
  • I/O栈

    • Linux存储系统的I/O栈,从上至下整体分为三个层次:文件系统层、通用块层、设备层
    • 文件系统层

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

      • 主要功能

        • 向上,为文件系统和应用程序提供访问块设备的标准接口;向下,把各种异构的磁盘设备抽象为统一的块设备,并提供统一框架来管理这些设备的驱动程序
        • 给上层发来的 I/O 请求排队,并通过重新排序、请求合并等方式,提高磁盘读写的效率
      • 包括块设备 I/O 队列和 I/O 调度器。它会对文件系统的 I/O 请求进行排队,再通过重新排序和请求合并,然后才要发送给下一级的设备层

        • I/O调度算法:NONE、NOOP、CFQ 以及 DeadLine

          • NONE,相当于没算法。它完全不使用任何 I/O 调度器,对上层的 I/O 不做任何处理
          • NOOP ,是最简单的一种 I/O 调度算法。它实际上是一个先入先出的队列,只做一些最基本的请求合并
          • CFQ(Completely Fair Scheduler),也被称为完全公平调度器,是现在很多发行版的默认 I/O 调度器,它为每个进程维护了一个 I/O 调度队列,并按照时间片来均匀分布每个进程的 I/O 请求(支持优先级调度)
          • DeadLine,分别为读、写请求创建了不同的 I/O 队列,可以提高机械磁盘的吞吐量,并确保达到最终期限(deadline)的请求被优先处理
    • 设备层

      • 包括存储设备和相应的驱动程序,负责最终物理设备的 I/O 操作
  • 磁盘性能指标

    • 使用率,是指磁盘处理 I/O 的时间百分比。过高的使用率(比如超过 80%),通常意味着磁盘 I/O 存在性能瓶颈。
    • 饱和度,是指磁盘处理 I/O 的繁忙程度。过高的饱和度,意味着磁盘存在严重的性能瓶颈。当饱和度为 100% 时,磁盘无法接受新的 I/O 请求。
    • IOPS(Input/Output Per Second),是指每秒的 I/O 请求数。
    • 吞吐量,是指每秒的 I/O 请求大小。
    • 响应时间,是指 I/O 请求从发出到收到响应的间隔时间。
  • 磁盘I/O观测工具

    • 系统维度:iostat

      • 命令范例:iostat -d -x 2 10(-d显示磁盘情况,-x显示详细信息,每隔2秒输出10次)
      • r/s,每秒发送给磁盘的读请求数量(合并后)
      • w/s,每秒发送给磁盘的写请求数量(合并后)
      • rkB/s,每秒从磁盘读取的数据量(单位kB)
      • wkB/s,每秒写入磁盘的数据量(单位kB)
      • rrqm/s,每秒合并的读请求数(%rrqm表示合并读请求的百分比)
      • wrqm/s,每秒合并的写请求数(%rrqm表示合并写请求的百分比)
      • avgrq-sz,请求队列中的平均大小
      • avgqu-sz,平均请求队列长度
      • r_await,读请求处理完成等待时间(包括队列中的等待时间和设备实际处理实际,单位ms)
      • w_await,写请求处理完成等待时间(包括队列中的等待时间和设备实际处理实际,单位ms)
      • await,即r_await和w_await的平均值
      • svctm,处理I/O请求所需的平均实际(不包括等待时间,单位ms,该时间只是推断)
      • %util,磁盘处理I/O的时间百分比
    • 进程维度:pidstat

      • 命令范例:pidstat -d 2 10
      • 每秒读取的数据大小(kB_rd/s) ,单位是 KB。
      • 每秒发出的写请求数据大小(kB_wr/s) ,单位是 KB。
      • 每秒取消的写请求数据大小(kB_ccwr/s) ,单位是 KB。

【案例】如何找出狂打日志的“内鬼”

  • 排查经过

    • 通过top,查看系统整体性能

      • (CPU iowait%占比高,内存buffer/cache容量大,说明是I/O问题)
    • 通过iostat,查看系统整体I/O性能

      • 命令:iostat -d -x 2 10
      • (sda磁盘的 I/O使用率%util大,wkB/s、w_await数值高,说明sda磁盘有写瓶颈)
    • 通过pidstat,查看可疑进程的I/O性能

      • 命令:pidstat -d
      • (某进程的kB_wr/s数值很高,说明该进程一直在写I/O)
    • 通过strace,查看某进程的系统调用

      • 命令:strace -p
      • (发现该进程确实在写某个文件)
    • 通过lsof,查看进程打开的文件

      • 命令:lsof -p
      • (发现确实在打开某个文件疯狂写入)
    • 查看该进程的源代码,定位写文件的代码

【总结】如何迅速分析出系统I/O的瓶颈在哪里

  • 文件系统I/O性能指标

    • 存储空间容量、使用量、剩余空间
    • 索引节点容量、使用量、剩余空间
    • 页缓存、目录项缓存、索引节点缓存、具体文件系统缓存
    • IOPS(包括 r/s 和 w/s)、响应时间(延迟)、吞吐量(B/s)
  • 磁盘I/O性能指标

    • 使用率,是指磁盘忙处理 I/O 请求的百分比
    • IOPS(Input/Output Per Second),是指每秒的 I/O 请求数
    • 吞吐量,是指每秒的 I/O 请求大小
    • 响应时间,是指从发出 I/O 请求到收到响应的间隔时间
  • 性能工具

    • 系统维度

      • df:容量、使用量、剩余空间(加-i表示看索引,不加是磁盘;-h展现更好的容量单位)
      • /proc/meminfo:普通文件系统占用的缓存页Cached、可回收的slab的大小SReclaimable
      • /proc/slabinfo:目录项、索引节点、文件系统的缓存
      • slabtop:同上,但更直观
      • iostat:磁盘I/O使用率、IOPS、吞吐量、响应时间、平均队列长度与大小等等(-d显示磁盘情况,-x显示详细信息)
      • vmstat(加-d展示磁盘状态信息)
    • 进程维度

      • pidstat:进程读写I/O大小与延迟(加-d)
      • strace:查看某进程的系统调用
      • lsof:查看进程打开的文件
  • I/O问题整体分析思路

    • 先用 iostat 发现磁盘 I/O 性能瓶颈;
    • 再借助 pidstat ,定位出导致瓶颈的进程;
    • 随后分析进程的 I/O 行为;
    • 最后,结合应用程序的原理,分析这些 I/O 的来源。

【总结】磁盘 I/O 性能优化的几个思路

  • I/O基准测试

    • 基准测试工具:fio
    • 命令范例:fio -name=randread -direct=1 -iodepth=64 -rw=randread -ioengine=libaio -bs=4k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=/dev/sdb
    • 入参选项

      • direct,表示是否跳过系统缓存(1表示跳过)
      • iodepth,表示使用异步 I/O时,同时发出的 I/O 请求上限
      • rw,表示 I/O 模式( read/write 分别表示顺序读 / 写, randread/randwrite 则分别表示随机读 / 写)
      • ioengine,表示 I/O 引擎,支持同步(sync)、异步(libaio)、内存映射(mmap)、网络(net)等
      • bs,表示 I/O 的大小
      • filename,表示文件路径。它可以是磁盘路径(测试磁盘性能),或文件路径(测试文件系统性能)
    • 返回内容

      • slat ,是指从 I/O 提交到实际执行 I/O 的时长(Submission latency)
      • clat ,是指从 I/O 提交到 I/O 完成的时长(Completion latency)
      • lat ,指的是从 fio 创建 I/O 到 I/O 完成的总时长
  • 应用程序优化

    • 用追加写代替随机写,减少寻址开销,加快 I/O 写的速度
    • 借助缓存 I/O ,充分利用系统缓存,降低实际 I/O 的次数
    • 在应用程序内部构建自己的缓存,或者用 Redis 这类外部缓存系统
    • 在需要频繁读写同一块磁盘空间时,可以用 mmap 代替 read/write,减少内存的拷贝次数
    • 在需要同步写的场景中,尽量将写请求合并,而不是让每个请求都同步写入磁盘,即可以用 fsync() 取代 O_SYNC
    • 在多个应用程序共享相同磁盘时,为了保证 I/O 不被某个应用完全占用,推荐使用 cgroups 的 I/O 子系统,来限制进程 / 进程组的 IOPS 以及吞吐量
  • 文件系统优化

    • 根据实际负载场景的不同,选择最适合的文件系统
    • 优化文件系统的配置选项,包括文件系统的特性(如 ext_attr、dir_index)、日志模式(如 journal、ordered、writeback)、挂载选项(如 noatime)等

      • 调整文件系统的特性(tune2fs)
      • 调整文件系统的日志模式和挂载选项(/etc/fstab,mount)
    • 优化文件系统的缓存

      • 优化 pdflush 脏页的刷新频率(设置 dirty_expire_centisecs 和 dirty_writeback_centisecs)
      • 优化脏页的限额(调整 dirty_background_ratio 和 dirty_ratio 等)
    • 优化内核回收目录项缓存和索引节点缓存的倾向

      • 调整 /proc/sys/vm/vfs_cache_pressure(默认值 100),数值越大,就表示越容易回收
    • 在不需要持久化时,还可以用内存文件系统 tmpfs,以获得更好的 I/O 性能
  • 磁盘优化

    • 换用性能更好的磁盘,比如用 SSD 替代 HDD
    • 可以使用 RAID ,把多块磁盘组合成一个逻辑磁盘,构成冗余独立磁盘阵列
    • 针对磁盘和应用程序 I/O 模式的特征,我们可以选择最适合的 I/O 调度算法
    • 可以对应用程序的数据,进行磁盘级别的隔离
    • 在顺序读比较多的场景中,我们可以增大磁盘的预读数据

      • 调整内核选项 /sys/block/sdb/queue/read_ahead_kb,默认大小是 128 KB,单位为 KB。
      • 使用 blockdev 工具设置,比如 blockdev --setra 8192 /dev/sdb
    • 可以优化内核块设备 I/O 的选项

      • 调整磁盘队列的长度 /sys/block/sdb/queue/nr_requests,适当增大队列长度

标签: none

添加新评论