Linux性能优化实战笔记(I/O篇)
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
- (发现该进程确实在写某个文件)
- 命令:strace -p
通过lsof,查看进程打开的文件
- 命令:lsof -p
- (发现确实在打开某个文件疯狂写入)
- 命令: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,适当增大队列长度