Disk Diagnosis
Last updated
Last updated
文件系统层:包括虚拟文件系统和各种文件系统的具体实现。
通用块层:包括块设备 I/O 队列和 I/O 调度器。
设备层:包括存储设备和相应的驱动程序,负责最终物理设备的 I/O 操作。
I/O 通常是整个系统中最慢的一环,Linux 提供多种缓存机制来优化 I/O。如文件系统使用了页缓存、索引节点缓存、目录项缓存;块设备使用了缓冲区。
文件系统在磁盘的基础上,提供了一个用来管理文件的树状结构。Linux 为每个文件都分配两个数据结构,索引节点(index node)和目录项(directory entry):
索引节点,inode:记录文件的元数据,如 inode 编号、文件大小、访问权限、修改日期、数据位置等。inode 和文件一一对应,会被持久化到磁盘。在磁盘格式化的时候设定好的。
目录项,dentry:记录文件的名字、inode 指针、与其它 dentry 的关联关系。由内核维护的内存数据结构,也叫目录项缓存。
dentry 与 inode 是多对一的关系,即一个文件可以有多个别名。通过硬链接为文件创建别名,就会有不同的目录项,这些目录项的索引节点相同。
磁盘读写的最小单位是扇区(512B),文件系统把连续的扇区组成逻辑块,以逻辑块为最小单元来管理数据。常见的逻辑块为 4KB,即 8 个连续扇区。
内存一节提到文件内容会缓存到页缓存 Cache 中,索引节点也会缓存到内存中。另外,磁盘在执行文件系统格式化时,会分成三个存储区,超级块会存储整个文件系统的状态。
内核使用 slab 机制管理目录项和索引节点。
可通过 df 查看磁盘容量。free, vmstat, /proc/meminfo, /proc/slabinfo, slabtop 等工具查看文件的缓存信息。
为了支持各种不同的文件系统,Linux 内核在用户进程和文件系统中,引入一个 VFS(Virtual File System) 抽象层。VFS 定义了一组标准的数据结构和接口,所以用户进程使用时不需要关心底层文件系统的实现细节。
Linux 支持多种文件系统,这些文件系统首先得挂载到 VFS 目录树中的某个子目录(挂载点),然后才能访问。支持的文件系统主要分为:
基于磁盘:Ext4、XFS、OverlayFS 等。
基于内存:/proc、/sys 等。
网络:NFS、SMB、iSCSI 等。
是否利用标准库缓存:
缓冲 IO:使用标准库缓存来加速文件访问,标准库内部再通过系统调用访问文件。
非缓冲 IO:直接通过系统调用。
是否利用操作系统的页缓存:
直接 IO:跳过操作系统的页缓存,直接与文件系统交互。需要指定 O_DIRECT。
非直接 IO:文件读写时,先经过页缓存。默认选项。
直接 IO 与非直接 IO 都是与文件系统交互。跳过文件系统直接读写磁盘叫做裸 IO。
应用程序是否阻塞自身运行(应用程序的角度):
阻塞 IO:应用程序执行 IO 操作后,在获取响应之前会阻塞当前线程。
非阻塞 IO:执行 IO 后,不阻塞,可以执行其它任务。随后通过轮询或事件通知的方式获取调用结果。
系统响应 IO 请求的方式(内核系统的角度):
同步 IO:系统收到 IO 请求后,等 IO 完成后才告知应用程序的 IO 结果。
异步 IO:系统受到 IO 请求后,立即告诉应用程序请求已收到,随后处理 IO,处理完成后,通过事件通知的方式告诉应用程序结果。
O_SYNC,O_DSYNC 代表同步,O_SYNC 是在 O_DSYNC 基础上,文件元数据写入磁盘后才能返回。
常见的磁盘按存储介质可分为两类:
机械磁盘:HDD(Hard Disk Driver),由盘片和磁头组成,数据存储在盘片的环状磁道中,读写数据时需要先移动磁头到位置。最小读写单位是扇区,一般是 512B。
固态磁盘:SSD(Solid State Disk),不需要磁道寻址,连续 I/O 和随机 I/O 性能都比 HDD 好得多。最小读写单位是页,一般是 4KB、8KB 等。
文件系统会把连续的扇区或块组成逻辑块,常见逻辑块大小是 4KB,即 8 个扇区或一个页。
同类磁盘的随机 I/O 都比连续 I/O 慢很多:
HDD:随机 I/O 需要更多的磁头寻道和盘片旋转。
SSD:存在“先擦除再写入”的机制,随机 I/O 导致大量地垃圾回收。
连续 I/O 有预读机制,可以减少 I/O 次数。
按接口分类一般有:IDE、SCSI、SAS、SATA、FC 等。不同接口有不同的设备名称,IDE 会有 hd 前缀,SCSI 和 SATA 会有 sd 前缀。若有多块同类型磁盘,则按照 a, b, c 来编号。磁盘还可以划分逻辑分区,每个分区再用数字编号,如 /dev/sda1, /dev/sda2。
还可以把多块磁盘组成逻辑磁盘,即 RAID。RAID0 读写性能最优,但不提供数据冗余;其它级别的 RAID,提供数冗余,读写也有一定优化。
Linux 每个块设备都会有主、次设备号,主设备号用于驱动程序,次设备号用于给多个同类设备编号。
文件系统有 VFS,通用 Linux 提供一个统一的统一块层来管理不同的块设备,是处于文件系统和磁盘驱动中间的一个块设备抽象层。主要有两个功能:
为文件系统提供访问块设备的标准接口。
对文件系统和应用程序发来的 I/O 请求排队,通过重新排序、请求合并等方式提高磁盘读写效率。
对 I/O 请求排序,就是 I/O 调度。Linux 内核提供四种调度算法:
NONE:不使用任何调度器。常用于虚拟机,磁盘 I/O 调度又物理机负责。
NOOP:先入先出,仅做一些基本的请求合并,常用于 SSD。
CFQ(Completely Fair Scheduler):很多发行版的默认选项,为每个进程维护一个 I/O 调度队列,按时间片均匀分布每个进程的 I/O 请求。
DeadLine:为读写请求创建不同的 I/O 队列,多用于 I/O 压力比较重的场景,如数据库。
使用率:磁盘处理 I/O 的时机百分比。一般超过 80% 意味着性能瓶颈。
饱和度:磁盘处理 I/O 的繁忙程度。当饱和度为 100% 时,磁盘无法接受新的 I/O 请求。
IOPS(Input/Output Per Second):每秒的 I/O 请求数。
吞吐量:每秒的 I/O 请求大小。
响应时间:I/O 请求发起到响应的间隔时间。
使用率仅考虑有没有 I/O,不考虑 I/O 大小。当使用率达到 100% 时,仍可能接受新的 I/O 请求。
性能指标需要多个综合分析,如数据库、小文件随机读写较多的场景,IOPS 更好;多媒体等顺序读写较多的场景,吞吐量更好。
磁盘基准测试可用 fio。磁盘指标可用 iostat、pidstat、iotop、/proc/diskstats 等工具查看。
先运行输出指标较多的工具,如 iostat、vmstat、pidstat 等,再缩小范围
追加写替代随机写
借助缓存 IO,充分利用系统缓存
程序内部构建自己的缓存。如 C 标准库 fopen、fread 会利用库函数的缓存;直接使用 open、read 等系统调用时,就只能利用系统的页缓存和缓冲区。
用 mmap 代替 read、write。
fsync() 替代 O_SYNC。
使用 cgroups 的 IO 子系统。
在使用 CFQ 调度器时,使用 ionice 调整优先级。
选择合适的文件系统。
优化文件系统的配置。特性(ext_addr, dir_index),日志模式(journal, ordered, writeback),挂载(noatime)等。tune2fs 调整特性,/etc/fstab 或 mount 调整日志模式和挂载。
优化文件系统缓存。
pdflush 脏页的刷新频率。如 dirty_expire_centisecs, dirty_writeback_centisecs
pdflush 脏页的限额。如 dirty_background_ratio, dirty_ratio
内核目录项缓存和索引节点缓存。vfs_cache_pressure
若不需要持久化,可以用内存文件系统 tmpfs。如 /dev/shm。
SSD 替换 HDD。
使用 RAID。
选择合适的 IO 调度算法。SSD 和虚拟机磁盘使用 noop,数据库使用 deadline。
磁盘隔离。比如日志和数据可以放不同的盘。
若顺序读比较多,可以增大预读数据。
调整 /sys/block/sdb/queue/read_ahead_kb
使用 blockdev 工具。
优化内核块设备 IO 选项。如磁盘队列长度 /sys/block/sdb/queue/nr_requests
查看磁盘是否有硬件问题。dmesg 查看硬件 IO 错误日志;badblocks、smartctl 等工具检查硬件问题;e2fsck 检查文件系统错误;fsck 修复错误等。