# Disk Diagnosis

* 文件系统层：包括虚拟文件系统和各种文件系统的具体实现。
* 通用块层：包括块设备 I/O 队列和 I/O 调度器。
* 设备层：包括存储设备和相应的驱动程序，负责最终物理设备的 I/O 操作。

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MMEymzPK-eCICVZ2br1%2F-MMFA9xBQfsPvY8-t2zA%2Fimage.png?alt=media\&token=1ee277c8-b15d-40a2-bc15-04dba48df584)

I/O 通常是整个系统中最慢的一环，Linux 提供多种缓存机制来优化 I/O。如文件系统使用了页缓存、索引节点缓存、目录项缓存；块设备使用了缓冲区。

## 文件系统

### 索引节点和目录项

文件系统在磁盘的基础上，提供了一个用来管理文件的树状结构。Linux 为每个文件都分配两个数据结构，索引节点（index node）和目录项（directory entry）：

* **索引节点，inode**：记录文件的元数据，如 inode 编号、文件大小、访问权限、修改日期、数据位置等。inode 和文件一一对应，会被持久化到磁盘。在磁盘格式化的时候设定好的。
* **目录项，dentry**：记录文件的名字、inode 指针、与其它 dentry 的关联关系。由内核维护的**内存**数据结构，也叫目录项缓存。

dentry 与 inode 是多对一的关系，即一个文件可以有多个别名。通过硬链接为文件创建别名，就会有不同的目录项，这些目录项的索引节点相同。

磁盘读写的最小单位是扇区（512B），文件系统把连续的扇区组成**逻辑块**，以逻辑块为最小单元来管理数据。常见的逻辑块为 4KB，即 8 个连续扇区。

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MLpD5BVOS2X-g8PdL0C%2F-MLvWASfgNBQ4voJe3lB%2Fimage.png?alt=media\&token=412d7266-cd19-4b2a-b686-d16a089e60be)

内存一节提到文件内容会缓存到页缓存 Cache 中，索引节点也会缓存到内存中。另外，磁盘在执行文件系统格式化时，会分成三个存储区，**超级块**会存储整个文件系统的状态。

内核使用 slab 机制管理目录项和索引节点。

可通过 df 查看磁盘容量。free, vmstat, /proc/meminfo, /proc/slabinfo, slabtop 等工具查看文件的缓存信息。

### VFS

为了支持各种不同的文件系统，Linux 内核在用户进程和文件系统中，引入一个 VFS（Virtual File System） 抽象层。VFS 定义了一组标准的数据结构和接口，所以用户进程使用时不需要关心底层文件系统的实现细节。

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MLpD5BVOS2X-g8PdL0C%2F-MLvXIooqZzTnzaMe1mb%2Fimage.png?alt=media\&token=98feb8b4-5031-4b76-ab38-eeb083162f75)

Linux 支持多种文件系统，这些文件系统首先得挂载到 VFS 目录树中的某个子目录（挂载点），然后才能访问。支持的文件系统主要分为：

* 基于磁盘：Ext4、XFS、OverlayFS 等。
* 基于内存：/proc、/sys 等。
* 网络：NFS、SMB、iSCSI 等。

### I/O 分类

是否利用标准库缓存：

* 缓冲 IO：使用标准库缓存来加速文件访问，标准库内部再通过系统调用访问文件。
* 非缓冲 IO：直接通过系统调用。

是否利用操作系统的页缓存：

* 直接 IO：跳过操作系统的页缓存，直接与文件系统交互。需要指定 O\_DIRECT。
* 非直接 IO：文件读写时，先经过页缓存。默认选项。

{% hint style="info" %}
直接 IO 与非直接 IO 都是与文件系统交互。跳过文件系统直接读写磁盘叫做裸 IO。
{% endhint %}

应用程序是否阻塞自身运行（应用程序的角度）：

* 阻塞 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 请求发起到响应的间隔时间。

{% hint style="info" %}
使用率仅考虑有没有 I/O，不考虑 I/O 大小。当使用率达到 100% 时，仍可能接受新的 I/O 请求。
{% endhint %}

性能指标需要多个综合分析，如数据库、小文件随机读写较多的场景，IOPS 更好；多媒体等顺序读写较多的场景，吞吐量更好。

磁盘基准测试可用 fio。磁盘指标可用 iostat、pidstat、iotop、/proc/diskstats 等工具查看。

## 总结

### 指标

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MMPVjpRmJ6p12WAmcr1%2F-MMQCzzxhjR_Xks9K6FN%2Fimage.png?alt=media\&token=5f7f178b-0aa9-4eb9-a19e-0b609cdb50df)

### 指标 -> 工具

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MMPVjpRmJ6p12WAmcr1%2F-MMQDVcGRKt7miZ3kuz4%2Fimage.png?alt=media\&token=594b9eaf-5d5f-4af4-9664-ae52d8a23fc0)

### 工具 -> 指标

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MMPVjpRmJ6p12WAmcr1%2F-MMQGOQwbgKcM31qi7hL%2Fimage.png?alt=media\&token=379475eb-831b-4dde-828c-00dd35c83f36)

### 分析思路

先运行输出指标较多的工具，如 iostat、vmstat、pidstat 等，再缩小范围

![](https://3232244687-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LYZow-MmROshIrkwdtE%2F-MMPVjpRmJ6p12WAmcr1%2F-MMQGvn5Smj29J10WNag%2Fimage.png?alt=media\&token=14b33ad6-a775-45c1-bd66-03e2074ac5b8)

### 优化思路

#### 应用程序优化

* 追加写替代随机写
* 借助缓存 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 修复错误等。
