前一篇介绍了文件系统的工作原理,这一篇来看看磁盘IO的工作原理
一、 磁盘
1. 按存储介质分类
磁盘是可以持久化存储的设备,根据存储介质的不同,常见磁盘可以分为两类:机械磁盘和固态磁盘。
机械磁盘,也称为硬盘驱动器(Hard Disk Driver,HDD),主要由盘片和读写磁头组成,数据存储在盘片的环状磁道中。在读写数据前,需要移动读写磁头,定位到数据所在的磁道,才能访问数据。显然,如果 I/O 请求刚好连续,就不需要磁道寻址,自然可以获得最佳性能。这其实就是我们熟悉的连续 I/O 的工作原理。与之相对应的是随机 I/O,它需要不停地移动磁头,来定位数据位置,所以读写速度就会比较慢。
固态磁盘(Solid State Disk,SSD),由固态电子元器件组成,不需要磁道寻址。所以,不管是连续 I/O 还是随机 I/O 的性能,都比机械磁盘要好得多。不过,SSD存在“先擦除再写入”的限制。随机读写会导致大量的垃圾回收,所以随机 I/O 的性能比起连续 I/O 来,也还是差了很多。
此外,连续 I/O 还可以通过预读的方式,来减少 I/O 请求的次数,这也是其性能优异的一个原因。很多性能优化的方案,也都会从这个角度出发,来优化 I/O 性能。
2. 按接口分类
按照接口来分类,比如可以把硬盘分为 IDE(Integrated Drive Electronics)、SCSI(Small Computer System
Interface) 、SAS(Serial Attached SCSI) 、SATA(Serial ATA) 、FC(Fibre Channel) 等。
不同的接口,往往分配不同的设备名称。比如:IDE 设备会分配一个 hd 前缀的设备名,SCSI 和 SATA 设备会分配一个 sd 前缀的设备名。如果是多块同类型的磁盘,就会按照a、b、c 等的字母顺序来编号。
3. 按使用方式分类
除了磁盘本身的分类外,当你把磁盘接入服务器后,按照不同的使用方式,又可以把它们划分为多种不同的架构。
- 直接作为独立磁盘设备来使用。这些磁盘往往还会根据需要,划分为不同的逻辑分区,每个分区再用数字编号。比如 /dev/sda ,还可以分成两个分区 /dev/sda1 和 /dev/sda2。
- 多块磁盘组合成一个逻辑磁盘,构成 RAID,从而提高数据访问的性能,增强数据存储的可靠性。
- 把磁盘组合成一个网络存储集群,再通过 NFS、SMB、iSCSI 等网络存储协议,暴露给服务器使用。
在 Linux 中,磁盘实际上是作为一个块设备来管理的,以块为单位读写数据,并且支持随机读写。每个块设备都会被赋予两个设备号,分别是主、次设备号。主设备号用在驱动程序中,用来区分设备类型;次设备号则是用来给多个同类设备编号。
二、 通用块层
1. 简介与功能
跟 VFS 类似,为了减小不同块设备的差异带来的影响,Linux 通过一个统一的通用块层,来管理各种不同的块设备。
通用块层,其实是处在文件系统和磁盘驱动中间的一个块设备抽象层。它主要有两个功能:
- 第一个功能跟虚拟文件系统的功能类似。向上,为文件系统和应用程序提供访问块设备的标准接口;向下,把各种异构的磁盘设备抽象为统一的块设备,并提供统一框架来管理这些设备的驱动程序。
- 第二个功能,给文件系统和应用程序发来的 I/O 请求排队,并通过重新排序、请求合并等方式,提高磁盘读写的效率。
2. I/O 调度算法
其中第二种功能,对 I/O 请求排序的过程,也就是我们熟悉的 I/O 调度。Linux 内核支持四种 I/O 调度算法,分别是 NONE、NOOP、CFQ 以及 DeadLine。
- NONE:确切来说,并不能算 I/O 调度算法,因为它完全不使用任何 I/O 调度器,对文件系统和应用程序的 I/O 不做任何处理,常用在虚拟机中(此时磁盘 I/O 调度完全由物理机负责)。
- NOOP :最简单的 I/O 调度算法。它实际上是一个先入先出的队列,只做一些最基本的请求合并,常用于 SSD 磁盘。
- CFQ(Completely Fair Scheduler),完全公平调度器,是现在很多发行版的默认 I/O 调度器,它为每个进程维护了一个 I/O 调度队列,并按照时间片来均匀分布每个进程的 I/O 请求。类似于进程 CPU 调度,CFQ 还支持进程 I/O 的优先级调度,所以它适用于运行大量进程的系统,像是桌面环境、多媒体应用等。
- DeadLine:分别为读、写请求创建了不同的 I/O 队列,可以提高机械磁盘的吞吐量,并确保达到deadline的请求被优先处理。多用在 I/O 压力比较重的场景,比如数据库等。
三、 I/O 栈
清楚了磁盘和通用块层的工作原理,再结合上一期的文件系统原理,我们就可以整体来看 Linux 存储系统的 I/O 原理了。
我们可以把 Linux 存储系统的 I/O 栈,由上到下分为三个层次,分别是文件系统层、通用块层和设备层。这三个 I/O 层的关系如下图所示,这其实也是 Linux 存储系统的 I/O 栈全景图
根据这张 I/O 栈的全景图,我们可以更清楚地理解,存储系统 I/O 的工作原理。
- 文件系统层,包括虚拟文件系统和其他各种文件系统的具体实现。它为上层的应用程序,提供标准的文件访问接口;对下会通过通用块层,来存储和管理磁盘数据。
- 通用块层,包括块设备 I/O 队列和 I/O 调度器。它会对文件系统的 I/O 请求进行排队,再通过重新排序和请求合并,然后才要发送给下一级的设备层。
- 设备层,包括存储设备和相应的驱动程序,负责最终物理设备的 I/O 操作
存储系统的 I/O ,通常是整个系统中最慢的一环。所以, Linux 通过多种缓存机制来优化I/O 效率。比方说,为了优化文件访问的性能,会使用页缓存、索引节点缓存、目录项缓存等多种缓存机制,以减少对下层块设备的直接调用。同样,为了优化块设备的访问效率,会使用缓冲区,来缓存块设备的数据。
四、 磁盘性能指标
1. 衡量指标
衡量磁盘性能的五个常见指标:使用率、饱和度、IOPS、吞吐量以及响应时间。
- 使用率,是指磁盘处理 I/O 的时间百分比。过高的使用率(比如超过 80%),通常意味着磁盘 I/O 存在性能瓶颈。
- 饱和度,是指磁盘处理 I/O 的繁忙程度。过高的饱和度,意味着磁盘存在严重的性能瓶颈。当饱和度为 100% 时,磁盘无法接受新的 I/O 请求。
- IOPS,每秒的 I/O 请求数。
- 吞吐量,是指每秒的 I/O 请求大小。
- 响应时间,是指 I/O 请求从发出到收到响应的间隔时间。
这里要注意的是,使用率只考虑有没有 I/O,而不考虑 I/O 的大小。换句话说,当使用率是100% 的时候,磁盘依然有可能接受新的 I/O 请求。
不要孤立地去比较某一指标,而要结合读写比例、I/O 类型(随机还是连续)以及I/O 的大小,综合来分析。举个例子,在数据库、大量小文件等这类随机读写比较多的场景中,IOPS 更能反映系统的整体性能;而在多媒体等顺序读写较多的场景中,吞吐量才更能反映系统的整体性能。
2. 磁盘 I/O 观测
iostat 是最常用的磁盘 I/O 性能观测工具,它提供了每个磁盘的使用率、IOPS、吞吐量等各种常见的性能指标,这些指标实际上来自 /proc/diskstats。
- # -d -x表示显示所有磁盘I/O的指标
- $ iostat -d -x 1
- 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
- sda 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
- sdb 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
- %util ,就是我们前面提到的磁盘 I/O 使用率;
- r/s+ w/s ,就是 IOPS;
- rkB/s+wkB/s ,就是吞吐量;
- r_await+w_await ,就是响应时间。
- 在观测指标时,也别忘了结合请求的大小( rareq-sz 和 wareq-sz)一起分析。
iostat 只提供磁盘整体的 I/O 性能数据,缺点在于,并不能知道具体是哪些进程在进行磁盘读写。
要观察进程的 I/O 情况,你还可以使用 pidstat 和 iotop 这两个工具。
- 用户 ID(UID)和进程 ID(PID) 。
- 每秒读取的数据大小(kB_rd/s) ,单位是 KB。
- 每秒发出的写请求数据大小(kB_wr/s) ,单位是 KB。
- 每秒取消的写请求数据大小(kB_ccwr/s) ,单位是 KB。
- 块 I/O 延迟(iodelay),包括等待同步块 I/O 和换入块 I/O 结束的时间,单位是时钟周期。
iotop类似于 top,根据 I/O 大小对进程排序,方便找到 I/O 较大的进程。
- $ iotop
- Total DISK READ : 0.00 B/s | Total DISK WRITE : 7.85 K/s
- Actual DISK READ: 0.00 B/s | Actual DISK WRITE: 0.00 B/s
- TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
- 15055 be/3 root 0.00 B/s 7.85 K/s 0.00 % 0.00 % systemd-journald
前两行:进程的磁盘读写大小总数和磁盘真实的读写大小总数。因为缓存、缓冲区、I/O合并等的影响,它们可能并不相等。
剩下的部分,包括线程 ID、I/O 优先级、每秒读磁盘的大小、每秒写磁盘的大小、换入和等待 I/O 的时钟百分比等。