计算机的运行,就像一场有条不紊的交响乐演奏,CPU、内存、磁盘等硬件各司其职,共同奏响和谐的旋律。在这场演奏中,磁盘 I/O 虽然不像 CPU 那样被众人熟知,却承担着至关重要的角色。它就像一座桥梁,连接着计算机的内部世界与外部存储,负责数据的输入与输出。从打开一份文档,到加载一款大型游戏,再到服务器处理海量数据,每一个操作都离不开磁盘 I/O 的默默支持。
然而,你是否想过,为什么有时候打开一个文件会瞬间完成,而有时候却要等待许久?为什么同样是存储设备,固态硬盘和机械硬盘的读写速度会有天壤之别?磁盘 I/O 背后,究竟隐藏着怎样的奥秘?
接下来,就让我们一同深入探索磁盘 I/O 的世界,揭开它神秘的面纱,了解它的工作原理、性能指标,以及影响它的关键因素,让你对计算机的存储系统有全新的认知 。
一、磁盘I/O是什么?
磁盘是可以持久化存储的设备,根据存储介质的不同,常见磁盘可以分为两类:机械磁盘和固态磁盘。
第一类,机械磁盘,也称为硬盘驱动器(Hard Disk Driver),通常缩写为 HDD。机械磁盘主要由盘片和读写磁头组成,数据就存储在盘片的环状磁道中。在读写数据前,需要移动读写磁头,定位到数据所在的磁道,然后才能访问数据。显然,如果 I/O 请求刚好连续,那就不需要磁道寻址,自然可以获得最佳性能。这其实就是我们熟悉的,连续 I/O 的工作原理。与之相对应的,当然就是随机 I/O,它需要不停地移动磁头,来定位数据位置,所以读写速度就会比较慢。
第二类,固态磁盘(Solid State Disk),通常缩写为 SSD,由固态电子元器件组成。固态磁盘不需要磁道寻址,所以,不管是连续 I/O,还是随机 I/O 的性能,都比机械磁盘要好得多。
其实,无论机械磁盘,还是固态磁盘,相同磁盘的随机 I/O 都要比连续 I/O 慢很多,原因也很明显。
- 对机械磁盘来说,我们刚刚提到过的,由于随机 I/O 需要更多的磁头寻道和盘片旋转,它的性能自然要比连续 I/O 慢。
- 而对固态磁盘来说,虽然它的随机性能比机械硬盘好很多,但同样存在“先擦除再写入”的限制。随机读写会导致大量的垃圾回收,所以相对应的,随机 I/O 的性能比起连续 I/O 来,也还是差了很多。
- 此外,连续 I/O 还可以通过预读的方式,来减少 I/O 请求的次数,这也是其性能优异的一个原因。很多性能优化的方案,也都会从这个角度出发,来优化 I/O 性能
此外,机械磁盘和固态磁盘还分别有一个最小的读写单位。
- 机械磁盘的最小读写单位是扇区,一般大小为 512 字节。
- 而固态磁盘的最小读写单位是页,通常大小是 4KB、8KB 等。
如果每次都读写 512 字节这么小的单位的话,效率很低。所以,文件系统会把连续的扇区或页,组成逻辑块,然后以逻辑块作为最小单元来管理数据。常见的逻辑块的大小是 4KB,也就是说,连续 8 个扇区,或者单独的一个页,都可以组成一个逻辑块。
除了可以按照存储介质来分类,另一个常见的分类方法,是按照接口来分类,比如可以把硬盘分为 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 等的字母顺序来编号;除了磁盘本身的分类外,当你把磁盘接入服务器后,按照不同的使用方式,又可以把它们划分为多种不同的架构。
最简单的,就是直接作为独立磁盘设备来使用。这些磁盘,往往还会根据需要,划分为不同的逻辑分区,每个分区再用数字编号。比如我们前面多次用到的 /dev/sda ,还可以分成两个分区 /dev/sda1 和 /dev/sda2。
另一个比较常用的架构,是把多块磁盘组合成一个逻辑磁盘,构成冗余独立磁盘阵列,也就是 RAID(Redundant Array of Independent Disks),从而可以提高数据访问的性能,并且增强数据存储的可靠性;根据容量、性能和可靠性需求的不同,RAID 一般可以划分为多个级别,如 RAID0、RAID1、RAID5、RAID10 等。
- RAID0 有最优的读写性能,但不提供数据冗余的功能。
- 而其他级别的 RAID,在提供数据冗余的基础上,对读写性能也有一定程度的优化。
最后一种架构,是把这些磁盘组合成一个网络存储集群,再通过 NFS、SMB、iSCSI 等网络存储协议,暴露给服务器使用。
其实在 Linux 中,磁盘实际上是作为一个块设备来管理的,也就是以块为单位读写数据,并且支持随机读写。每个块设备都会被赋予两个设备号,分别是主、次设备号。主设备号用在驱动程序中,用来区分设备类型;而次设备号则是用来给多个同类设备编号。
简单来说,磁盘 I/O 就是计算机与磁盘之间进行数据输入和输出的过程。当你在电脑上保存一个文档时,这就是一个数据输出(写入)到磁盘的操作;而当你打开这个文档时,就是从磁盘输入(读取)数据到计算机内存的过程。磁盘就像是一个仓库,而 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 效率;比方说,为了优化文件访问的性能,会使用页缓存、索引节点缓存、目录项缓存等多种缓存机制,以减少对下层块设备的直接调用。同样,为了优化块设备的访问效率,会使用缓冲区,来缓存块设备的数据。
- 平时调用write的时候,数据是从应用写入到了C标准库的IO Buffer(用户态),这个Buffer在应用内存中,应用挂了,数据就没了;
- 在关闭流之前调用flush,通过flush将数据主动写入到内核的Page Cache中,应用挂了,数据也安全(内核态),但是系统挂了数据就没了;
将内核中的Page Cache中的数据写入到磁盘(缓存)中,系统挂了,数据也不丢失,需要调用fsync(持久化介质)。
总体来说,这就是操作系统的多级缓存和数据的可用性。操作系统也是程序,靠着多线程、异步、多级缓存实现高性能。
三、磁盘I/O的性能指标
机械硬盘的连续读写性能很好,但随机读写性能很差,这主要是因为磁头移动到正确的磁道上需要时间,随机读写时,磁头需要不停的移动,时间都浪费在了磁头寻址上,所以性能不高。衡量磁盘的重要主要指标是IOPS和吞吐量。
(1)吞吐量
吞吐量(Throughput),指单位时间内可以成功传输的数据数量。顺序读写频繁的应用,如视频点播,关注连续读写性能、数据吞吐量是关键衡量指标。它主要取决于磁盘阵列的架构,通道的大小以及磁盘的个数。不同的磁盘阵列存在不同的架构,但他们都有自己的内部带宽,一般情况下,内部带宽都设计足够充足,不会存在瓶颈。磁盘阵列与服务器之间的数据通道对吞吐量影响很大,比如一个2Gbps的光纤通道,其所能支撑的最大流量仅为250MB/s。最后,当前面的瓶颈都不再存在时,硬盘越多的情况下吞吐量越大。
(2)IOPS
IOPS(Input/Output Per Second)即每秒的输入输出量(或读写次数),即指每秒内系统能处理的I/O请求数量。随机读写频繁的应用,如小文件存储等,关注随机读写性能,IOPS是关键衡量指标。可以推算出磁盘的IOPS = 1000ms / (Tseek + Trotation + Transfer),如果忽略数据传输时间,理论上可以计算出随机读写最大的IOPS。
常见磁盘的随机读写最大IOPS为:
- 7200rpm的磁盘 IOPS = 76 IOPS
- 10000rpm的磁盘IOPS = 111 IOPS
- 15000rpm的磁盘IOPS = 166 IOPS
(3)响应时间
响应时间是指从发出 I/O 请求到收到响应的时间间隔。它包括了寻道时间(机械硬盘)、旋转延迟(机械硬盘)以及数据传输时间等。响应时间越短,用户体验就越好,比如打开文件的速度就越快。
四、影响磁盘I/O的性能因素
Linux 磁盘 I/O 性能受多种因素影响,包括硬件设备、文件系统、系统配置和应用程序的 I/O 模式等。以下是一些主要的影响因素:
(1)硬件设备
硬件是磁盘 I/O 性能的基础,包括硬盘的类型(HDD 或 SSD)、转速、缓存大小以及接口类型(如 SATA、NVMe)等。
# 查看磁盘信息
lsblk
(2)文件系统
文件系统的选择和配置对 I/O 性能有显著影响。EXT4、XFS 和 Btrfs 是 Linux 中常用的文件系统,每个文件系统都有其特点和最佳使用场景。
# 查看文件系统类型
df -T
(3)系统配置
Linux 内核参数、I/O 调度器、文件系统的挂载选项等系统配置都会影响磁盘 I/O 性能。
# 查看当前I/O调度器
cat /sys/block/sda/queue/scheduler
(4)应用程序 I/O 模式
应用程序的 I/O 模式,包括 I/O 大小、读写比例、同步异步 I/O 等,也会对磁盘 I/O 性能产生影响。示例代码:C语言中同步和异步I/O的简单比较
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
// 同步I/O操作
FILE *fp = fopen("testfile", "w+");
fprintf(fp, "Data to write");
fclose(fp);
// 异步I/O操作(在此示例中仅示意,实际实现更复杂)
// ...
return 0;
}
五、磁盘I/O性能评估工具
为了准确评估磁盘 I/O 性能,选择合适的工具至关重要。以下是一些常用的磁盘 I/O 性能评估工具及其使用方法。
(1)iostat是一个用于监控系统输入 / 输出设备和 CPU 使用的工具,可以提供磁盘读写速度、I/O 操作次数等关键性能指标
# 安装iostat(以CentOS为例)
sudo yum install sysstat
# 使用iostat查看磁盘I/O性能
iostat -mx 1
(2)iotop是一个用来监视 I/O 使用情况的工具,可以显示哪些进程正在使用磁盘,以及它们的 I/O 速率。
# 安装iotop(以CentOS为例)
sudo yum install iotop
# 使用iotop监视I/O使用情况
iotop -o
(3)dd是一个经典的命令行工具,用于复制文件并转换和格式化数据。它也可以用来测试磁盘的读写性能。
# 使用dd命令测试写性能
dd if=/dev/zero of=testfile bs=1M count=1024 oflag=dsync
# 使用dd命令测试读性能
dd if=testfile of=/dev/null bs=1M count=1024 iflag=nocache
(4)fio是一个灵活的 I/O 测试工具,可以生成各种不同类型的负载,以测试磁盘的 I/O 性能。
# 使用fio进行磁盘I/O测试
fio --name=test --ioengine=libaio --iodepth=4 --rw=readwrite --bs=4k --size=1G --numjobs=1
通过使用这些工具,可以收集磁盘 I/O 性能的相关数据,为进一步的性能优化提供依据;应用程序在进行 I/O 操作时,可以采用异步 I/O、缓存等技术,减少 I/O 等待时间,提高整体性能。
六、磁盘缓存与I/O缓存策略
磁盘缓存和 I/O 缓存策略是提升磁盘 I/O 性能的重要手段。合理配置和使用缓存,可以显著减少磁盘的物理读写次数,加快数据访问速度。
6.1 磁盘缓存
磁盘缓存是位于硬盘和内存之间的小容量高速存储区域,它可以临时存储频繁访问的数据,以减少对硬盘的访问次数。
(1)启用磁盘缓存
大多数现代硬盘都带有内置缓存,通常情况下操作系统会自动管理这些缓存。在某些情况下,管理员可能需要调整缓存策略。
# 查看磁盘缓存策略
hdparm -c /dev/sda
# 启用磁盘缓存(对于支持此功能的硬盘)
hdparm -W 1 /dev/sda
(2)监控磁盘缓存效果
监控磁盘缓存的效果可以帮助管理员了解缓存的使用情况和性能改进。
# 使用iostat监控磁盘缓存命中率
iostat -dx 1
6.2 I/O 缓存策略
I/O 缓存策略涉及操作系统如何使用内存作为缓存来优化磁盘 I/O 操作。Linux 提供了多种机制来管理 I/O 缓存。
(1)调整文件系统缓存,通过调整 vm.dirty_ratio 和 vm.dirty_background_ratio 参数,可以控制文件系统缓存的大小和写入策略。
# 查看当前缓存参数
cat /proc/sys/vm/dirty_ratio
cat /proc/sys/vm/dirty_background_ratio
# 调整缓存参数
echo 20 > /proc/sys/vm/dirty_ratio
echo 10 > /proc/sys/vm/dirty_background_ratio
(2)使用直接 I/O,直接 I/O 允许应用程序绕过操作系统缓存,直接向磁盘写入数据。这在某些场景下可以提高性能,但也会增加磁盘 I/O 的压力。
# 使用dd命令进行直接I/O操作
dd if=/dev/zero of=testfile bs=1M count=1024 oflag=dsync
(3)异步 I/O 与缓存,异步 I/O 可以与缓存策略结合使用,以提高应用程序的响应性和磁盘 I/O 效率。
// 示例代码:结合异步I/O和缓存策略
#include <stdio.h>
#include <stdlib.h>
#include <aio.h>
int main() {
struct aiocb aiocb;
// 初始化异步I/O控制块,并设置异步操作标志...
// 执行异步I/O操作...
aio_read(&aiocb);
// 处理其他任务,操作系统会在后台处理I/O操作...
// 等待异步操作完成...
aio_wait(&aiocb);
return 0;
}
通过合理配置磁盘缓存和 I/O 缓存策略,可以显著提升 Linux 系统的磁盘 I/O 性能,尤其是在高负载和频繁访问的数据场景中。管理员需要根据具体的应用需求和系统负载,调整和优化缓存策略。
七、磁盘阵列与RAID技术
磁盘阵列技术通过将多个物理磁盘组合成一个逻辑单元来提高存储系统的性能和可靠性。RAID(Redundant Array of Independent Disks)是一种常见的磁盘阵列实现,它通过不同的数据分布和冗余策略来提升磁盘 I/O 性能和数据安全性。
7.1 RAID 级别
RAID 有多种级别,每种级别都有其特定的性能和冗余特点。以下是一些常见的 RAID 级别:
(1)RAID 0:通过数据条带化(striping)来提高读写速度,但不提供数据冗余。它适用于需要高速存储但数据安全性不高的场景。
# 创建RAID 0设备(需要mdadm工具)
mdadm --create /dev/md0 --level=0 --raid-devices=2 /dev/sda /dev/sdb
(2)RAID 1:通过镜像(mirroring)来实现数据冗余,提高了数据的可靠性,但空间利用率只有 50%。它适用于对数据安全性要求高的场景。
# 创建RAID 1设备
mdadm --create /dev/md1 --level=1 --raid-devices=2 /dev/sda /dev/sdb
(3)RAID 5:结合了条带化和分布式奇偶校验,提供了性能提升和数据冗余。它适用于需要平衡性能和冗余的场景。
# 创建RAID 5设备
mdadm --create /dev/md5 --level=5 --raid-devices=3 /dev/sda /dev/sdb /dev/sdc
(4)RAID 10:
RAID 10 是 RAID 1 和 RAID 0 的组合,提供了高性能和数据冗余。它适用于对性能和数据安全性都有较高要求的场景。
# 创建RAID 10设备
mdadm --create /dev/md10 --level=10 --raid-devices=4 /dev/sda /dev/sdb /dev/sdc /dev/sdd
7.2 管理 RAID 设备
使用 mdadm
工具可以管理 RAID 设备,包括创建、监控和修复 RAID 阵列。
# 查看RAID设备状态
cat /proc/mdstat
# 修复RAID设备(如果某个磁盘出现问题)
mdadm --manage /dev/md0 --rebuild-map
7.3 监控 RAID 性能
监控 RAID 性能对于确保存储系统的高效运行至关重要。可以使用 iostat
、mdstat
和其他工具来监控 RAID 设备的性能。
# 使用iostat监控RAID设备性能
iostat -dxm /dev/md0 1
7.4 RAID 与磁盘 I/O 性能
RAID 技术可以显著提升磁盘 I/O 性能,尤其是在读写操作上。选择合适的 RAID 级别和配置对于最大化性能至关重要。同时,合理的 RAID 配置也可以提高数据的可靠性和容错能力。
通过使用磁盘阵列和 RAID 技术,系统管理员可以提升 Linux 系统的磁盘 I/O 性能,同时确保数据的安全性和可靠性。在选择 RAID 级别时,需要综合考虑性能需求、数据冗余要求和成本等因素。