You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
iotop
# 进程的磁盘读写大小总数和磁盘真实的读写大小总数。因为缓存、缓冲区、I/O 合并等因素的影响,可能并不相等。# 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 # 线程 ID、I/O 优先级、每秒读磁盘的大小、每秒写磁盘的大小、换入和等待 I/O 的时钟百分比等。# 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
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
{% pullquote mindmap mindmap-md %}
{% endpullquote %}
先附上 CPU、内存、磁盘、网络各种操作执行时间开销的直观对比:
文件系统与磁盘
由于磁盘是 块设备,可被划分为不同的 分区,在磁盘或者分区上可再创建 文件系统,并挂载到系统的某个目录中,系统通过挂载目录来读写文件。
Linux 中一切皆文件,因此可通过相同的文件接口来访问磁盘和文件。磁盘读写的原理:磁盘读写的最小单位是扇区(512B),为了提高读写效率,文件系统把连续的扇区组成逻辑块,每次都以逻辑块(常见 4KB,即连续的 8 个扇区)为最小单元来管理数据。其有两种 I/O 方式:
普通文件 :I/O 请求首先经过文件系统,由文件系统与磁盘交互。
块设备文件:跳过文件系统、直接与磁盘交互,即 裸 I/O。
两种 I/O 方式使用的缓存不同,文件系统管理的缓存是 Cache 的一部分,裸磁盘的缓存则是使用 Buffer。
文件系统
Linux 文件系统的四大基本要素:目录项、索引节点、逻辑块、超级块,接下来是具体的解析。
索引节点和目录项
文件系统 是对存储设备上的文件进行组织管理的机制。由于 Linux 中一切皆文件,除了普通的文件和目录,块设备、套接字、管道等也都通过统一的文件系统来管理,并使用两个数据结构记录文件元信息和目录结构:
索引节点(index node,即 inode):用来记录文件的元数据,比如 inode 编号、文件大小、访问权限、修改日期、数据位置 等,与文件一一对应,并会被持久化存储到磁盘中(因此也占用磁盘空间)。
目录项(directory entry,即 entry):用来记录 文件名称、索引节点指针 以及 与其他目录项的关联关系(树状结构)。多个关联的目录项构成文件系统的目录结构。目录项是由内核维护的一个内存数据结构,通常也称为 目录项缓存。
目录项和索引节点的关系是多对一,一个文件可以有多个别名(比如建立硬链接为文件创建别名)。由于前者是内存缓存数据,后者是磁盘数据,为了协调磁盘与 CPU 的性能差异,文件内容会缓存到页缓存 Cache 中。
磁盘在执行文件系统格式化时,会被分成三个存储区域:
超级块:存储整个文件系统的状态。
索引节点区:存储索引节点。
数据块区:存储文件数据。
虚拟文件系统(VFS)
即为了支持不同的文件系统,Linux 内核在用户进程和文件系统之间引入的抽象层。
VFS 定义一组所有文件系统都支持的数据结构和标准接口。用户进程和内核中的其他子系统与 VFS 提供的统一接口进行交互,而不需要再关心底层文件系统的实现细节。
VFS 支持的各种文件系统可分为三类:
磁盘文件系统:把数据直接存储在计算机本地挂载的磁盘中,如 Ext4、XFS、OverlayFS 等。
内存文件系统:即虚拟文件系统,不需要磁盘分配存储空间、但会占用内存。比如
/proc
、/sys
(向用户空间导出层次化的内核对象)。网络文件系统:用于访问其他计算机数据的文件系统,如 NFS、SMB、iSCSI 等。
文件系统要先挂载到 VFS 目录树中的某个子目录(挂载点)才能访问其中的文件。比如在安装系统时,要先挂载一个根目录(/),在根目录下再把其他文件系统(其他的磁盘分区、/proc、/sys、NFS 等)挂载进来。
Page Cache
已知应用程序通过系统调用向内核发起 I/O 操作,在进入块 I/O 层之前有以下几种情况:
Memory-Mapped I/O:通过内存映射把数据写入 Page Cache。
Buffered I/O:由 VFS 处理系统调用,把数据写入 Page Cache。
Direct I/O:由 VFS 处理系统调用,不经过 Page Cache 直接写入块 I/O 层。
因此 Page Cache 是内核态内存,不属于用户。
文件系统 I/O
文件系统被挂载后,就可以通过挂载点访问其管理的文件。VFS 有标准的文件访问接口,以系统调用的方式提供给应用程序使用。比如
cat
命令,就是依次调用open()
、read()
、write()
函数来执行:以读取磁盘文件内容、通过网卡发送的场景为例。涉及以下几个环节:
其中从内核态到用户态再到内核态涉及 2 次拷贝,再加上读磁盘和写网卡一共 4 次拷贝。
由于用户进程缓冲区空间有限(比如 32KB),要发送一个大文件(假设 320MB)就需要 320MB / 32KB 共 10,000 次才能完成,即 40,000 次上下文切换。
直接与非直接 I/O
直接 I/O:跳过操作系统页缓存,直接与文件系统交互访问文件。
非直接 I/O:文件读写时经过系统页缓存,再由内核或额外的系统调用真正写入磁盘。
在系统调用中指定 O_DIRECT 标志即实现直接 I/O(默认非直接 I/O)。直接 I/O、非直接 I/O,本质上还是和文件系统交互;在数据库等场景中也有裸 I/O,即跳过文件系统读写磁盘的情况。
零拷贝:属于非直接 I/O,其特点是无需经过用户缓冲区(用户态),即只有 2 次上下文切换,3 次内存拷贝;如果网卡支持 SG-DMA 技术,则无需 Socket 缓冲区,可以进一步优化为 2 次上下文切换和 2 次内存拷贝。
零拷贝有以下特点:
开发者无需关心 Socket 缓冲区大小,把发送字节数设为文件未发送字节数(比如 320MB),则一次性经网卡发出的数据量为 Socket 缓冲区大小(比如 1.4MB),上下文切换次数和内存拷贝次数约为 2 * 320MB / 1.4MB 共 457 次,拷贝数据量也只有 640MB(耗费 CPU 资源更少)。
利用 Page Cache:Page Cahce 基于 LRU 缓存最近访问的数据,效率远高于访问磁盘。再加上预读出更多数据,在淘汰出 Page Cache 前就被进程访问,发挥更大收益。
使用零拷贝不允许进程在发送操作前对文件内容进行加工(比如压缩)。
当文件太大,文件中某一部分内容被再次访问的概率非常低,却耗费 CPU 或 DMA 的多一次拷贝;而且大文件快速把 Page Cache 占满,其它热点小文件就无法利用 Page Cache。
直接 I/O 的应用场景:
由应用程序实现磁盘文件缓存,避免 Page Cache 额外的性能消耗(比如 MySQL)。
高并发下传输大文件,本身难以命中 Page Cache、带来额外的内存拷贝、挤占小文件 I/O 的内存,使用 直接 I/O 更合理。
除此之外,直接 I/O 无法享用基于 Page Cache 的 磁盘预读 和 I/O 合并(即内核 I/O 调度试图在 Page Cache 中缓存尽量多的连续 I/O,在合并成大 I/O 后再一次发给磁盘以减少寻址操作)带来的性能提升,因此应用场景很有限。
阻塞与非阻塞 I/O
阻塞 / 非阻塞针对的是 I/O 调用者(即应用程序)。
阻塞 I/O:应用程序执行 I/O 操作后如没有获得响应就会阻塞当前线程,不能执行其他任务。
非阻塞 I/O:应用程序执行 I/O 操作后不会阻塞当前的线程,可以继续执行其它任务,随后再通过轮询或者事件通知的形式获取调用结果。
比如访问管道或者网络套接字时,设置 O_NONBLOCK 标志就表示用非阻塞方式访问(默认阻塞,即
send()
操作的线程阻塞,无法去做其它事)。如果使用 epoll,系统会告诉该套接字的状态,因此可以用非阻塞的方式使用。当这个套接字不可写时,可以转去做其它事,比如读写其它套接字。
同步与异步 I/O
同步 / 异步针对的是 I/O 执行者(即系统)。
同步 I/O:应用程序发起 I/O 操作后要等到整个 I/O 完成后,才能获得 I/O 响应。
异步 I/O:应用程序发起 I/O 操作后不等待完成,I/O 完成后响应由内核以事件通知应用程序。
操作文件时设置 O_DSYNC(等文件数据写入磁盘后才能返回)或 O_SYNC(在前者基础上要求文件元数据也要写入磁盘后才能返回)标志就代表同步 I/O。比如访问管道或者网络套接字时设置了 O_ASYNC 选项,相应的 I/O 就是异步 I/O,内核会再通过 SIGIO 或者 SIGPOLL,来通知进程文件是否可读写。
异步 I/O 解决了阻塞问题,但其实现上存在无法利用 PageCache 的缺陷,只支持直接 I/O。
缓冲与非缓冲 I/O
缓冲指标准库内部实现的缓存,由于不论是否被标准库缓存,最终还是经过系统调用来访问文件,系统调用后再通过页缓存来减少磁盘的 I/O 操作。
缓冲 I/O:利用标准库缓存加速文件访问,标准库内部再通过系统调度访问文件。
非缓冲 I/O:直接通过系统调用来访问文件,不经过标准库缓存。
如何监控?
使用
df
查看文件系统磁盘空间、索引节点使用情况:通过
/proc/meminfo
查看缓存大小:内核使用 Slab 机制管理目录项和索引节点的缓存。
/proc/meminfo
只给出了 Slab 的整体大小,查看/proc/slabinfo
可查看 Slab 缓存具体情况(所有目录项和各种文件系统索引节点的缓存):使用
slabtop
查看目录项和索引节点占用的 Slab 缓存。磁盘 I/O
磁盘是可以持久化存储的设备,根据存储介质分类:
机械磁盘(Hard Disk Driver,HDD):最小读写单位为扇区(一般 512Bytes)。主要由盘片和读写磁头组成,数据就存储在盘片的环状磁道中。在读写数据前需要移动读写磁头,定位到数据所在的磁道才能访问数据。如果 I/O 请求刚好连续则不需要磁道寻址,且可以通过预读的方式减少 I/O 次数获得最佳性能。与之相对随机 I/O 需要更多的磁头寻道和盘片旋转,读写速度较慢。
固态磁盘 (Solid State Disk,SSD):最小读写单位为页(一般 4KB、8KB)。由固态电子元器件组成,不需要磁道寻址,连续 I/O 和随机 I/O 的性能都比机械磁盘好。但同样存在“先擦除再写入”的限制,随机读写导致大量的垃圾回收,所以随机 I/O 的性能比起连续 I/O 来差很多。
根据接口来分类: 不同的接口往往分配不同的设备名称。IDE(Integrated Drive Electronics,前缀 hd)、SCSI(Small Computer System Interface,前缀 sd) 、SAS(Serial Attached SCSI,前缀 sd) 、SATA(Serial ATA,前缀 sd) 、FC(Fibre Channel) 等。多块同类型的磁盘会按照 a、b、c 等的字母顺序来编号。
根据架构分类:把磁盘接入服务器后,按照不同的使用方式可以把它们划分为多种不同的架构。
直接作为独立磁盘设备来使用,在磁盘上划分不同的逻辑分区(/dev/sda1)。
把多块磁盘组合成一个逻辑磁盘,构成冗余独立磁盘阵列(Redundant Array of Independent Disks,RAID),以提高读写性能或提供冗余。
把磁盘组合成网络存储集群,再通过 NFS、SMB、iSCSI 等网络存储协议暴露给服务器使用。
在 Linux 中磁盘以块为单位读写数据(块设备),并支持随机读写。每个块设备在驱动程序中被赋予主设备号(用来区分设备类型)和次设备号(给多个同类设备编号)。
通用块层
即在文件系统和磁盘驱动中间的一个块设备抽象层。
与虚拟文件系统的功能类似。向上为文件系统和应用程序提供访问块设备的标准接口;向下把各种异构的磁盘设备抽象为统一的块设备,并提供统一框架来管理这些设备的驱动程序。
I/O 调度:给文件系统和应用程序发来的 I/O 请求排队,并通过重新排序、请求合并等方式提高磁盘读写的效率。
I/O 调度算法:
NONE:对文件系统和应用程序的 I/O 其实不做任何处理,常用在虚拟机中(此时磁盘 I/O 调度完全由物理机负责)。
NOOP:一个先入先出的队列,只做一些最基本的请求合并,常用于 SSD 磁盘。
CFQ:完全公平调度器(Completely Fair Scheduler),通常是默认的 I/O 调度器。为每个进程维护了一个 I/O 调度队列,按照时间片均匀分布每个进程的 I/O 请求;另外还支持进程 I/O 的优先级调度,适用于运行大量进程的系统。
DeadLine:分别为读、写请求创建 I/O 队列,提高机械磁盘的吞吐量,并确保达到最终期限的请求被优先处理。多用在 I/O 压力比较重的场景,比如数据库等。
I/O 栈
存储系统 I/O 的工作原理:
文件系统层:虚拟文件系统和物理文件系统。为上层应用程序提供标准的文件访问接口;对下通过通用块层来存储和管理磁盘数据。
通用块层:包括块设备 I/O 队列和 I/O 调度器。会对文件系统的 I/O 请求进行排队,再通过重新排序和请求合并,然后才要发送给下一级的设备层。
设备层:包括存储设备和相应的驱动程序,负责最终物理设备的 I/O 操作。
存储系统的 I/O 是整个系统中最慢的部分,Linux 通过多种缓存机制来优化 I/O 效率:
优化文件访问:使用页缓存、索引节点缓存、目录项缓存等多种缓存机制减少对下层块设备的直接调用。
优化块设备访问:使用缓冲区来缓存块设备的数据。
磁盘性能指标
衡量磁盘性能需要考虑:
使用率:磁盘处理 I/O 的时间百分比。过高的使用率(80%+)通常意味着磁盘 I/O 存在性能瓶颈。只考虑是否有 I/O,不考虑 I/O 大小。
饱和度:磁盘处理 I/O 的繁忙程度。过高的饱和度意味着磁盘存在严重的性能瓶颈(100% 时磁盘无法接受新的 I/O 请求),大量顺序读写场景下更能反映系统性能(比如多媒体)。
IOPS(Input/Output Per Second):每秒的 I/O 请求数,大量随机读写场景下更能反映系统性能(比如数据库、大量小文件)。
吞吐量:每秒的 I/O 请求大小。
响应时间:I/O 请求从发出到收到响应的间隔时间。
以上指标可使用工具
fio
结合具体应用场景(顺序读/写,随机读/写)进行基准测试来评估。如何监控?
使用
iostat
命令查看每个磁盘的使用率、IOPS、吞吐量等(数据源于/proc/diskstats
):使用
pidstat
观察进程 I/O 情况:使用
iotop
根据 I/O 大小对进程进行排序:优化总结
应用优化
大文件异步 I/O 和直接 I/O,小文件零拷贝(文件大小的阈值可灵活配置,参考 Nginx 的 directio 指令)。
文件系统优化
根据实际负载场景选择最适合的文件系统。比如相比于 ext4(Ubuntu 默认),xfs(CentOS 默认)支持更大的磁盘分区(>16TB)和更大的文件数量,但缺点在于无法收缩,而 ext4 则可以。
优化文件系统的配置选项。包括文件系统的特性(如 ext_attr、dir_index)、日志模式(如 journal、ordered、writeback)、挂载选项(如 noatime)等。比如使用
tune2fs
可以调整文件系统的特性(也可以查看文件系统超级块的内容)。 而通过/etc/fstab
或mount
命令行参数,可以调整文件系统的日志模式和挂载选项等。优化文件系统的缓存。比如可以优化 pdflush 脏页的刷新频率(设置 dirty_expire_centisecs 和 dirty_writeback_centisecs)以及脏页的限额(调整 dirty_background_ratio 和 dirty_ratio 等)。还可以优化内核回收目录项缓存和索引节点缓存的倾向,即调整 vfs_cache_pressure(
/proc/sys/vm/vfs_cache_pressure
,默认值 100),数值越大表示越容易回收。使用内存文件系统 tmpfs 以获得更好的 I/O 性能 。tmpfs 把数据直接保存在内存中,而不是磁盘中。比如
/dev/shm
就是大多数 Linux 默认配置的一个内存文件系统,大小默认为总内存的一半,在不需要持久化时建议使用。磁盘优化
换用性能更好的磁盘,比如用 SSD 替代 HDD。
使用 RAID 把多块磁盘组合成一个逻辑磁盘,构成冗余独立磁盘阵列。可以提高数据的可靠性和数据的访问性能。
针对磁盘和应用程序 I/O 模式的特征,选择最适合的 I/O 调度算法。比如 SSD 和虚拟机中的磁盘通常用 noop 调度算法。而数据库应用更推荐使用 deadline 算法。
对应用程序的数据进行磁盘级别的隔离。比如可以为日志、数据库等 I/O 压力比较重的应用,配置单独的磁盘。
在顺序读比较多的场景中可以增大磁盘的预读数据,比如调整 /dev/sdb 的预读大小:
调整选项
/sys/block/sdb/queue/read_ahead_kb
,默认大小是 128 KB,单位为 KB。使用
blockdev
工具设置,比如blockdev --setra 8192 /dev/sdb
,单位是 512B(0.5KB),其数值总是 read_ahead_kb 两倍。优化内核块设备 I/O 的选项。比如可以适当增大磁盘队列长度
/sys/block/sdb/queue/nr_requests
,以提升磁盘的吞吐量(也会导致 I/O 延迟增大)。发现性能急剧下降时还需要确认磁盘本身是否出现硬件错误:使用
dmesg
查看是否有硬件 I/O 故障的日志;使用badblocks
、``smartctl等工具检测磁盘硬件问题,或用 e2fsck 等来检测文件系统的错误;如果发现问题可以使用
fsck` 等工具来修复。参考
分析案例可参考:
26 | 案例篇:如何找出狂打日志的“内鬼”?
27 | 案例篇:为什么我的磁盘I/O延迟很高?
28 | 案例篇:一个SQL查询要15秒,这是怎么回事?
29 | 案例篇:Redis响应严重延迟,如何解决?
Beta Was this translation helpful? Give feedback.
All reactions