文件系统
文件系统本身就是组织和管理存储设备上的文件的机制。不同的组织方式将形成不同的文件系统。Linux 中的一切都是文件。不仅是普通的文件和目录,还有一个统一的文件系统,如设备、套接字、管道等。
便于管理,Linux 文件系统为每个文件都分配两个数据结构,。它们主要用于记录文件的元信息和目录结构。
- 索引节点,简称 inode,用于记录文件的元数据,如 inode 编号、文件大小、访问权限、修改日期、数据位置等。索引节点与文件一一对应,与文件内容内容一样存储在磁盘中。因此,请记住,索引节点也占用磁盘空间。
- 目录项,简称 dentry,用于记录文件名称、索引节点指针和与其他目录项的关联。多个相关目录项构成了文件系统的目录结构。然而,与索引节点不同,目录项是由内核维护的内存数据结构,因此通常称为目录项缓存。
换句话说,目录项与索引节点的关系是多对一的,你可以简单地理解一个文件可以有多个别名。
但是这里有两点需要你注意。
第一,在前面的 Buffer 和 Cache 原理中提到,为了协调慢速磁盘和快速 CPU 性能差异,文件内容将缓存到页面上Cache 中间。这些索引节点自然会缓存到内存中,加速文件访问。
第二,当执行文件系统格式化时,磁盘将分为超级块、索引节点区和数据块三个存储区域。
- 存储整个文件系统状态的超级块。
- 用于存储索引节点的索引节点。
- 数据块区,则用来存储文件数据。
另外,磁盘读写的最小单位是扇区,但扇区只有 512B 大小,文件系统将连续的风扇区域形成逻辑块,然后每次以逻辑块为最小单元管理数据。
如何观察文件系统中的目录项和索引节点缓存?
实际上,内核使用 Slab 机制,管理目录项和索引节点的缓存。proc/meminfo 只给了 Slab 的整体尺寸具体到每个 Slab 缓存,还要检查/proc/slabinfo
本文件。但是输出内容混乱,我们可以使用它slabtop来查看
# 按下 c 按缓存大小排序a 按活动对象排序$ slabtop Active / Total Objects (% used) .4%) Active / Total Slabs (% used) .0%) Active / Total Caches (% used) .5%) Active / Total Size (% used) .88K / 73307.70K (78.9%) Minimum / Average / Maximum Object : 0.01K / 0.20K / 22.88K OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME 69804 23094 0% .19K 3324 21 13266326K dentry 16380 15854 .59K 1260 13 10080080008000800800K inode_cache 58260 5397 0% .13K 1942 30 K kernfs_node_cache 485 413 0% 5555 .69K 5 3104443104K task_struct 1472 1397 0% 22.00K 16 2944444444K kmalloc-2048
如上所示,目录项和索引节点占用最多的 Slab 缓存。然而,它们占用的内存并不大,只有 23MB 左右。
虚拟文件系统
构成了目录项、索引节点、逻辑块和超级块Linux 文件系统的四个基本要素。但是,为了支持不同的文件系统,Linux 内核在用户过程和文件系统之间引入抽象层,即虚拟文件系统 VFS(Virtual File System)。
VFS 定义了一组所有文件系统都支持的数据结构和标准接口。这样,用户进程和内核中的其他子系统,只需要跟 VFS 提供的统一供的统一接口,而无需关注底层各种文件系统的实现细节。
如上图 VFS 的下方,Linux 支持各种文件系统,如 Ext4、XFS、NFS 等等。这些文件系统根据存储位置可分为三类。
- 第一类是基于磁盘的文件系统,即将数据直接存储在计算机本地挂载的磁盘中。常见的 Ext4、XFS、OverlayFS 等,都是这样的文件系统。
- 第二类是基于内存的文件系统,通常被称为虚拟文件系统。这种文件系统不需要任何磁盘来分配存储空间,但会占用内存。proc 文件系统实际上是最常见的虚拟文件系统。此外,/sys 文件系统也属于这一类,主要将层次化的核心对象导出用户空间。
- 第三类是用于访问其他计算机数据的网络文件系统,如 NFS、SMB、iSCSI 等。
这些文件系统应该先挂载到 VFS 目录树中的子目录(称为挂载点),然后访问文件。以第一类为例,即基于磁盘的文件系统。安装系统时,应先挂载根目录(/),然后将其他文件系统(如其他磁盘分区/proc 文件系统//sys 文件系统, NFS 等)挂载。
磁盘
- 机械磁盘,又称硬盘驱动器(Hard Disk Driver),通常缩写为 HDD。机械磁盘主要由磁盘和读写磁头组成,数据存储在磁盘的环磁道中。在读写数据之前,在访问数据之前,移动读写磁头,定位数据所在的磁道。
- 固态磁盘(Solid State Disk),通常缩写为 SSD,它由固态电子元件组成。固态磁盘不需要磁道寻址,所以无论是连续 I/O,还是随机 I/O 的性能比机械磁盘好得多。
- 机械磁盘的最小读写单位是风扇区,一般大小为 512 字节。固态磁盘的最小读写单位是页面,通常大小为 4KB、8KB 等。
- 文件系统将连续的风扇区域或页面组成逻辑块,然后以逻辑块为最小单元管理数据。常见的逻辑块大小为 4KB,也就是说,一个逻辑块可以通过连续8 扇区或单独页面形成。
通用块层
与虚拟文件系统 VFS 类似地,为了减少不同块设备差异的影响,Linux 通过统一的通用块层管理各种块设备。它有两个功能:
- 第一个功能类似于虚拟文件系统。向上,为文件系统和应用程序提供访问块设备的标准接口;向下,将各种异构磁盘设备抽象成统一的块设备,并提供管理这些设备驱动程序的统一框架。
- 第二个功能,通用块层也会向文件系统和应用程序发送 I/O 请求排队,并通过重新排序合并,提高磁盘读写效率。
对 I/O 请求排序的过程是我们熟悉的 I/O 调度算法。这一层其实是由内核控制的。Linux 内核支持四种 I/O 调度算法分别是 NONE、NOOP、CFQ 以及 DeadLine。
把 Linux 存储系统的 I/O 栈由上到下分为文件系统层、通用块层和设备层三个层次。
- 文件系统层,包括虚拟文件系统和其他文件系统的具体实现。它为上层应用程序提供标准的文件访问接口;磁盘数据将通过通用块层存储和管理。
- 通用块层,包括块设备 I/O 队列和 I/O 调度器。它将对文件系统 I/O 请求排队,然后重新排序合并请求,然后发送到下一层设备层。
- 负责最终物理设备的设备层,包括存储设备和相应的驱动程序I/O 操作。
存储系统I/O ,通常是整个系统中最慢的部分。Linux 通过各种缓存机制优化 I/O 效率。例如,为了优化文件访问的性能,将使用页面缓存、索引节点缓存、目录项缓存等缓存机制,以减少对下块设备的直接呼叫。同样,为了优化块设备的访问效率,缓冲区将用于缓存块设备的数据。
磁盘性能指标
说到磁盘性能的衡量标准,必须提到使用率、饱和度、IOPS、吞吐量和响应时间。这五个指标是衡量磁盘性能的基本指标。
- 使用率是指磁盘处理 I/O 时间百分比。过高的利用率(如80%以上)通常意味着磁盘 I/O 性能瓶颈。
- 饱和度是指磁盘处理 I/O 忙。饱和度过高意味着磁盘存在严重的性能瓶颈。当饱和度为 100% 时,磁盘无法接受新的 I/O 请求。
- IOPS(Input/Output Per Second),指每秒 I/O 请求数。
- 吞吐量是指每秒 I/O 要求尺寸。
- 响应时间,指 I/O 请求从发出到收到响应的间隔。
这里需要注意的是,利用率只考虑是否有 I/O,而不考虑 I/O 的大小。换句话说,当利用率为 100% 时,磁盘仍然存在可能接受新的 I/O 请求。
性能优化指标
文件系统 I/O 性能指标
- 存储空间的使用情况,包括容量、使用量以及剩余空间等。
- 缓存使用情况,包括页缓存、目录项缓存、索引节点缓存以及各个具体文件系统(如 ext4、XFS 等)的缓存。
- 文件 I/O 也是很重要的性能指标,包括 IOPS(包括 r/s 和 w/s)、响应时间(延迟)以及吞吐量(B/s)等。在考察这类指标时,通常还要考虑实际文件的读写情况。比如,结合文件大小、文件数量、I/O 类型等,综合分析文件 I/O 的性能。
磁盘 I/O 性能指标
- 使用率,是指磁盘忙处理 I/O 请求的百分比。过高的使用率(比如超过 60%)通常意味着磁盘 I/O 存在性能瓶颈。
- IOPS(Input/Output Per Second),是指每秒的 I/O 请求数。
- 吞吐量,是指每秒的 I/O 请求大小。
- 响应时间,是指从发出 I/O 请求到收到响应的间隔时间。
根据指标找工具:
I/O 性能优化
应用程序优化
应用程序处于整个 I/O 栈的最上端,它可以通过系统调用,来调整 I/O 模式(如顺序还是随机、同步还是异步), 同时,它也是 I/O 数据的最终来源。在我看来,可以有这么几种方式来优化应用程序的 I/O 性能。
- 可以用追加写代替随机写,减少寻址开销,加快 I/O 写的速度。
- 可以借助缓存 I/O ,充分利用系统缓存,降低实际 I/O 的次数。
- 这样,一方面,能在应用程序内部,控制缓存的数据和生命周期;另一方面,也能降低其他应用程序使用缓存对自身的影响。比如, C 标准库提供的 fopen、fread 等库函数,都会利用标准库的缓存,减少磁盘的操作。而你直接使用 open、read 等系统调用时,就只能利用操作系统提供的页缓存和缓冲区等,而没有库函数的缓存可用
- 在需要频繁读写同一块磁盘空间时,可以用 mmap 代替 read/write,减少内存的拷贝次数。
- 在需要同步写的场景中,尽量将写请求合并,而不是让每个请求都同步写入磁盘,即可以用 fsync() 取代 O_SYNC。
- 在多个应用程序共享相同磁盘时,为了保证 I/O 不被某个应用完全占用,推荐你使用 cgroups 的 I/O 子系统,来限制进程 / 进程组的 IOPS 以及吞吐量。
- 在使用 CFQ 调度器时,可以用 ionice 来调整进程的 I/O 调度优先级,特别是提高核心应用的 I/O 优先级。ionice 支持三个优先级类:Idle、Best-effort 和 Realtime。其中, Best-effort 和 Realtime 还分别支持 0-7 的级别,数值越小,则表示优先级别越高。
文件系统优化
应用程序访问普通文件时,实际是由文件系统间接负责,文件在磁盘中的读写。所以,跟文件系统中相关的也有很多优化 I/O 性能的方式。
- 根据实际负载场景的不同,选择最适合的文件系统。比如 Ubuntu 默认使用 ext4 文件系统,而 CentOS 7 默认使用 xfs 文件系统。相比于 ext4 ,xfs 支持更大的磁盘分区和更大的文件数量,如 xfs 支持大于 16TB 的磁盘。但是 xfs 文件系统的缺点在于无法收缩,而 ext4 则可以。
- 在选好文件系统后,还可以进一步优化文件系统的配置选项,包括文件系统的特性(如 ext_attr、dir_index)、日志模式(如 journal、ordered、writeback)、挂载选项(如 noatime)等等。
- 可以优化文件系统的缓存。你可以优化 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 默认配置的一个内存文件系统,它的大小默认为总内存的一半。
磁盘优化
数据的持久化存储,最终还是要落到具体的物理磁盘中,同时,磁盘也是整个 I/O 栈的最底层。从磁盘角度出发,自然也有很多有效的性能优化方法。
- 最简单有效的优化方法,就是换用性能更好的磁盘,比如用 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 延迟增大)。 - 磁盘本身出现硬件错误,也会导致 I/O 性能急剧下降,所以发现磁盘性能急剧下降时,你还需要确认,磁盘本身是不是出现了硬件错误。可以查看 dmesg 中是否有硬件 I/O 故障的日志。 还可以使用 badblocks、smartctl 等工具,检测磁盘的硬件问题,或用 e2fsck 等来检测文件系统的错误。如果发现问题,你可以使用 fsck 等工具来修复。