Linux I/O

2021年11月23日 阅读数:7
这篇文章主要向大家介绍Linux I/O,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

Linux I/O 那些事儿 https://mp.weixin.qq.com/s/diKfeu1-Lr4ZA5Ky_66TZghtml

Linux I/O 那些事儿

王睿  腾讯技术工程  2021-11-17

 

介绍 Linux IO 的一些基本原理。node

做者:arraywang,腾讯 CSIGios

咱们先看一张图:web

图片

这张图大致上描述了 Linux 系统上,应用程序对磁盘上的文件进行读写时,从上到下经历了哪些事情。算法

这篇文章就以这张图为基础,介绍 Linux 在 I/O 上作了哪些事情。数据库

文件系统

什么是文件系统

文件系统,自己是对存储设备上的文件,进行组织管理的机制。组织方式不一样,就会造成不一样的文件系统。好比常见的 Ext四、XFS、ZFS 以及网络文件系统 NFS 等等。bootstrap

可是不一样类型的文件系统标准和接口可能各有差别,咱们在作应用开发的时候却不多关心系统调用如下的具体实现,大部分时候都是直接系统调用 openreadwriteclose 来实现应用程序的功能,不会再去关注咱们具体用了什么文件系统(UFS、XFS、Ext四、ZFS),磁盘是什么接口(IDE、SCSI,SAS,SATA 等),磁盘是什么存储介质(HDD、SSD)ubuntu

应用开发者之因此这么爽,各类复杂细节都不用管直接调接口,是由于内核为咱们作了大量的有技术含量的脏活累活。开始的那张图看到 Linux 在各类不一样的文件系统之上,虚拟了一个 VFS,目的就是统一各类不一样文件系统的标准和接口,让开发者可使用相同的系统调用来使用不一样的文件系统。缓存

文件系统如何工做(VFS)

Linux 系统下的文件

在 Linux 中一切皆文件。不只普通的文件和目录,就连块设备、套接字、管道等,也都要经过统一的文件系统来管理。安全

用 ls -l 命令看最前面的字符能够看到这个文件是什么类型

brw-r--r-- 1 root    root    1, 2 4月  25 11:03 bnod // 块设备文件
crw-r--r-- 1 root    root    1, 2 4月  25 11:04 cnod // 符号设备文件
drwxr-xr-x 2 wrn3552 wrn3552    6 4月  25 11:01 dir // 目录
-rw-r--r-- 1 wrn3552 wrn3552    0 4月  25 11:01 file // 普通文件
prw-r--r-- 1 root    root       0 4月  25 11:04 pipeline // 有名管道
srwxr-xr-x 1 root    root       0 4月  25 11:06 socket.sock // socket文件
lrwxrwxrwx 1 root    root       4 4月  25 11:04 softlink -> file // 软链接
-rw-r--r-- 2 wrn3552 wrn3552 0 4月  25 11:07 hardlink // 硬连接(本质也是普通文件)

Linux 文件系统设计了两个数据结构来管理这些不一样种类的文件:

  • inode(index node):索引节点
  • dentry(directory entry):目录项
inode 和 dentry

inode

inode 是用来记录文件的 metadata,所谓 metadata 在 Wikipedia 上的描述是 data of data,其实指的就是文件的各类属性,好比 inode 编号、文件大小、访问权限、修改日期、数据的位置等。

wrn3552@novadev:~/playground$ stat file
  文件:file
  大小:0               块:0          IO 块:4096   普通空文件
设备:fe21h/65057d      Inode:32828       硬连接:2
权限:(0644/-rw-r--r--)  Uid:( 3041/ wrn3552)   Gid:( 3041/ wrn3552)
最近访问:2021-04-25 11:07:59.603745534 +0800
最近更改:2021-04-25 11:07:59.603745534 +0800
最近改动:2021-04-25 11:08:04.739848692 +0800
建立时间:-

inode 和文件一一对应,它跟文件内容同样,都会被持久化存储到磁盘中。因此,inode 一样占用磁盘空间,只不过相对于文件来讲它大小固定且大小不算大。

dentry

dentry 用来记录文件的名字、inode 指针以及与其余 dentry 的关联关系。

wrn3552@novadev:~/playground$ tree
.
├── dir
│   └── file_in_dir
├── file
└── hardlink
  • 文件的名字:像 dir、file、hardlink、file_in_dir 这些名字是记录在 dentry 里的
  • inode 指针:就是指向这个文件的 inode
  • 与其余 dentry 的关联关系:其实就是每一个文件的层级关系,哪一个文件在哪一个文件下面,构成了文件系统的目录结构

不一样于 inode,dentry 是由内核维护的一个内存数据结构,因此一般也被叫作 dentry cache。

文件是如何存储在磁盘上的

图片

这里有张图解释了文件是如何存储在磁盘上的,首先,磁盘再进行文件系统格式化的时候,会分出来 3 个区:

  1. Superblock
  2. inode blocks
  3. data blocks

(其实还有 boot block,可能会包含一些 bootstrap 代码,在机器启动的时候被读到,这里忽略)其中 inode blocks 放的都是每一个文件的 inode,data blocks 里放的是每一个文件的内容数据。这里关注一下 superblock,它包含了整个文件系统的 metadata,具体有:

  1. inode/data block 总量、使用量、剩余量
  2. 文件系统的格式,属主等等各类属性

superblock 对于文件系统来讲很是重要,若是 superblock 损坏了,文件系统就挂载不了了,相应的文件也没办法读写。既然 superblock 这么重要,那确定不能只有一份,坏了就没了,它在系统中是有不少副本的,在 superblock 损坏的时候,可使用 fsck(File System Check and repair)来恢复。回到上面的那张图,能够很清晰地看到文件的各类属性和文件的数据是如何存储在磁盘上的:

  1. dentry 里包含了文件的名字、目录结构、inode 指针
  2. inode 指针指向文件特定的 inode(存在 inode blocks 里)
  3. 每一个 inode 又指向 data blocks 里具体的 logical block,这里的 logical block 存的就是文件具体的数据

这里解释一下什么是 logical block:

  1. 对于不一样存储介质的磁盘,都有最小的读写单元
  • /sys/block/sda/queue/physical_block_size
  1. HDD 叫作 sector(扇区),SSD 叫作 page(页面)
  2. 对于 hdd 来讲,每一个 sector 大小 512Bytes
  3. 对于 SSD 来讲每一个 page 大小不等(和 cell 类型有关),经典的大小是 4KB
  4. 可是 Linux 以为按照存储介质的最小读写单元来进行读写可能会有效率问题,因此支持在文件系统格式化的时候指定 block size 的大小,通常是把几个 physical_block 拼起来就成了一个 logical block
  • /sys/block/sda/queue/logical_block_size
  1. 理论上应该是 logical_block_size >= physical_block_size,可是有时候咱们会看到 physical_block_size = 4K,logical_block_size = 512B 状况,其实这是由于磁盘上作了一层 512B 的仿真(emulation)(详情可参考 512e 和 4Kn

ZFS

这里简单介绍一个普遍应用的文件系统 ZFS,一些数据库应用也会用到 ZFS,先看一张 zfs 的层级结构图:

图片

这是一张从底向上的图:

  1. 将若干物理设备 disk 组成一个虚拟设备 vdev(同时,disk 也是一种 vdev)
  2. 再将若干个虚拟设备 vdev 加到一个 zpool 里
  3. 在 zpool 的基础上建立 zfs 并挂载(zvol 能够先不看,咱们没有用到)

ZFS 的一些操做

建立 zpool

root@:~ # zpool create tank raidz /dev/ada1 /dev/ada2 /dev/ada3 raidz /dev/ada4 /dev/ada5 /dev/ada6
root@:~ # zpool list tank
NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
tank     11G   824K  11.0G        -         -     0%     0%  1.00x  ONLINE  -
root@:~ # zpool status tank
  pool: tank
 state: ONLINE
  scan: none requested
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          raidz1-0  ONLINE       0     0     0
            ada1    ONLINE       0     0     0
            ada2    ONLINE       0     0     0
            ada3    ONLINE       0     0     0
          raidz1-1  ONLINE       0     0     0
            ada4    ONLINE       0     0     0
            ada5    ONLINE       0     0     0
            ada6    ONLINE       0     0     0
  • 建立了一个名为 tank 的 zpool
  • 这里的 raidz 同 RAID5

除了 raidz 还支持其余方案:

图片

建立 zfs

root@:~ # zfs create -o mountpoint=/mnt/srev tank/srev
root@:~ # df -h tank/srev
Filesystem    Size    Used   Avail Capacity  Mounted on
tank/srev     7.1G    117K    7.1G     0%    /mnt/srev
  • 建立了一个 zfs,挂载到了 /mnt/srev
  • 这里没有指定 zfs 的 quota,建立的 zfs 大小即 zpool 大小

对 zfs 设置 quota

root@:~ # zfs set quota=1G tank/srev
root@:~ # df -h tank/srev
Filesystem    Size    Used   Avail Capacity  Mounted on
tank/srev     1.0G    118K    1.0G     0%    /mnt/srev

ZFS 特性

Pool 存储

上面的层级图和操做步骤能够看到 zfs 是基于 zpool 建立的,zpool 能够动态扩容意味着存储空间也能够动态扩容,并且能够建立多个文件系统,文件系统共享完整的 zpool 空间无需预分配。

事务文件系统

zfs 的写操做是事务的,意味着要么就没写,要么就写成功了,不会像其余文件系统那样,应用打开了文件,写入还没保存的时候断电,致使文件为空。zfs 保证写操做事务采用的是 copy on write 的方式:

图片

  • 当 block B 有修改变成 B1 的时候,普通的文件系统会直接在 block B 原地进行修改变成 B1
  • zfs 则会再另外一个地方写 B1,而后再在后面安全的时候对原来的 B 进行回收
  • 这样结果就不会出现 B 被打开而写失败的状况,大不了就是 B1 没写成功

这个特性让 zfs 在断电后不须要执行 fsck 来检查磁盘中是否存在写操做失败须要恢复的状况,大大提高了应用的可用性。

ARC 缓存

ZFS 中的 ARC(Adjustable Replacement Cache) 读缓存淘汰算法,是基于 IBM 的 ARP(Adaptive Replacement Cache) 演化而来。在一些文件系统中实现的标准 LRU 算法实际上是有缺陷的:好比复制大文件之类的线性大量 I/O 操做,致使缓存失效率猛增(大量文件只读一次,放到内存不会被再读,坐等淘汰)。

另外,缓存能够根据时间来进行优化(LRU,最近最多使用),也能够根据频率进行优化(LFU,最近最常使用),这两种方法各有优劣,可是没办法适应全部场景。

ARC 的设计就是尝试在 LRU 和 LFU 之间找到一个平衡,根据当前的 I/O workload 来调整用 LRU 多一点仍是 LFU 多一点。

ARC 定义了 4 个链表:

  1. LRU list:最近最多使用的页面,存具体数据
  2. LFU list:最近最常使用的页面,存具体数据
  3. Ghost list for LRU:最近从 LRU 表淘汰下来的页面信息,不存具体数据,只存页面信息
  4. Ghost list for LFU:最近从 LFU 表淘汰下来的页面信息,不存具体数据,只存页面信息

ARC 工做流程大体以下:

  1. LRU list 和 LFU list 填充和淘汰过程和标准算法同样
  2. 当一个页面从 LRU list 淘汰下来时,这个页面的信息会放到 LRU ghost 表中
  3. 若是这个页面一直没被再次引用到,那么这个页面的信息最终也会在 LRU ghost 表中被淘汰掉
  4. 若是这个页面在 LRU ghost 表中未被淘汰的时候,被再一次访问了,这时候会引发一次幽灵(phantom)命中
  5. phantom 命中的时候,事实上仍是要把数据从磁盘第一次放缓存
  6. 可是这时候系统知道刚刚被 LRU 表淘汰的页面又被访问到了,说明 LRU list 过小了,这时它会把 LRU list 长度加一,LFU 长度减一
  7. 对于 LFU 的过程也与上述过程相似

ZFS 参考资料

关于 ZFS 详细介绍能够参考:

磁盘类型

磁盘根据不一样的分类方式,有各类不同的类型。

磁盘的存储介质

根据磁盘的存储介质能够分两类(你们都很熟悉):

  • HDD(机械硬盘)
  • SSD(固态硬盘)

磁盘的接口

根据磁盘接口分类:

  • IDE (Integrated Drive Electronics)
  • SCSI (Small Computer System Interface)
  • SAS (Serial Attached SCSI)
  • SATA (Serial ATA)
  • ...

不一样的接口,每每分配不一样的设备名称。好比, IDE 设备会分配一个 hd 前缀的设备名,SCSI 和 SATA 设备会分配一个 sd 前缀的设备名。若是是多块同类型的磁盘,就会按照 a、b、c 等的字母顺序来编号。

Linux 对磁盘的管理

其实在 Linux 中,磁盘其实是做为一个块设备来管理的,也就是以块为单位读写数据,而且支持随机读写。每一个块设备都会被赋予两个设备号,分别是主、次设备号。主设备号用在驱动程序中,用来区分设备类型;而次设备号则是用来给多个同类设备编号。

g18-"299" on ~# ls -l /dev/sda*
brw-rw---- 1 root disk 8,  0 Apr 25 15:53 /dev/sda
brw-rw---- 1 root disk 8,  1 Apr 25 15:53 /dev/sda1
brw-rw---- 1 root disk 8, 10 Apr 25 15:53 /dev/sda10
brw-rw---- 1 root disk 8,  2 Apr 25 15:53 /dev/sda2
brw-rw---- 1 root disk 8,  5 Apr 25 15:53 /dev/sda5
brw-rw---- 1 root disk 8,  6 Apr 25 15:53 /dev/sda6
brw-rw---- 1 root disk 8,  7 Apr 25 15:53 /dev/sda7
brw-rw---- 1 root disk 8,  8 Apr 25 15:53 /dev/sda8
brw-rw---- 1 root disk 8,  9 Apr 25 15:53 /dev/sda9
  • 这些 sda 磁盘主设备号都是 8,表示它是一个 sd 类型的块设备
  • 次设备号 0-10 表示这些不一样 sd 块设备的编号

Generic Block Layer

图片

和 VFS 相似,为了对上层屏蔽不一样块设备的差别,内核在文件系统和块设备以前抽象了一个 Generic Block Layer(通用块层),有时候一些人也会把下面的 I/O 调度层并到通用块层里表述。

这两层主要作两件事:

  1. 跟 VFS 的功能相似。向上,为文件系统和应用程序,提供访问块设备的标准接口;向下,把各类异构的磁盘设备抽象为统一的块设备,并提供统一框架来管理这些设备的驱动程序
  2. 对 I/O 请求进行调度,经过从新排序、合并等方式,提升磁盘读写效率

下图是一个完整的 I/O 栈全景图:

图片

能够看到中间的 Block Layer 其实就是 Generic Block Layer,在图中能够看到 Block Layer 的 I/O 调度分为两类,分别表示单队列和多队列的调度:

  • I/O scheduler
  • blkmq

I/O 调度

老版本的内核里只支持单队列的 I/O scheduler,在 3.16 版本的内核开始支持多队列 blkmq,这里介绍几种经典的 I/O 调度策略。

单队列 I/O scheduler:

  • NOOP:事实上是个 FIFO 的队列,只作基本的请求合并
  • CFQ:Completely Fair Queueing,彻底公平调度器,给每一个进程维护一个 I/O 调度队列,按照时间片来均匀分布每一个进程 I/O 请求,
  • DeadLine:为读和写请求建立不一样的 I/O 队列,确保达到 deadline 的请求被优先处理

多队列 blkmq:

  • bfq:Budget Fair Queueing,也是公平调度器,不过不是按时间片来分配,而是按请求的扇区数量(带宽)
  • kyber:维护两个队列(同步/读、异步/写),同时严格限制发到这两个队列的请求数以保证相应时间
  • mq-deadline:多队列版本的 deadline

性能指标

通常来讲 I/O 性能指标有这几个:

  • 使用率:ioutil,指的是磁盘处理 I/O 的时间百分比,ioutil 只看有没有 I/O 请求,不看 I/O 请求的大小。ioutil 越高表示一直都有 I/O 请求,不表明磁盘没法响应新的 I/O 请求
  • IOPS:每秒的 I/O 请求数
  • 吞吐量/带宽:每秒的 I/O 请求大小,一般是 MB/s 或者 GB/s 为单位
  • 响应时间:I/O 请求发出到收到响应的时间
  • 饱和度:指的是磁盘处理 I/O 的繁忙程度。这个指标比较玄学,没有直接的数据能够表示,通常是根据平均队列请求长度或者响应时间跟基准测试的结果进行对比来估算

(在作基准测试时,还会分顺序/随机、读/写进行排列组合分别去测 IOPS 和带宽)

上面的指标除了饱和度外,其余均可以在监控系统中看到。Linux 也提供了一些命令来输出不一样维度的 I/O 状态:

  • iostat -d -x:看各个设备的 I/O 状态,数据来源 /proc/diskstats
  • pidstat -d:看近处的 I/O
  • iotop:相似 top,按 I/O 大小对进程排序

 

 

 

介绍 Linux IO 的一些基本原理。

做者:arraywang,腾讯 CSIG

咱们先看一张图:

图片

这张图大致上描述了 Linux 系统上,应用程序对磁盘上的文件进行读写时,从上到下经历了哪些事情。

这篇文章就以这张图为基础,介绍 Linux 在 I/O 上作了哪些事情。

文件系统

什么是文件系统

文件系统,自己是对存储设备上的文件,进行组织管理的机制。组织方式不一样,就会造成不一样的文件系统。好比常见的 Ext四、XFS、ZFS 以及网络文件系统 NFS 等等。

可是不一样类型的文件系统标准和接口可能各有差别,咱们在作应用开发的时候却不多关心系统调用如下的具体实现,大部分时候都是直接系统调用 openreadwriteclose 来实现应用程序的功能,不会再去关注咱们具体用了什么文件系统(UFS、XFS、Ext四、ZFS),磁盘是什么接口(IDE、SCSI,SAS,SATA 等),磁盘是什么存储介质(HDD、SSD)

应用开发者之因此这么爽,各类复杂细节都不用管直接调接口,是由于内核为咱们作了大量的有技术含量的脏活累活。开始的那张图看到 Linux 在各类不一样的文件系统之上,虚拟了一个 VFS,目的就是统一各类不一样文件系统的标准和接口,让开发者可使用相同的系统调用来使用不一样的文件系统。

文件系统如何工做(VFS)

Linux 系统下的文件

在 Linux 中一切皆文件。不只普通的文件和目录,就连块设备、套接字、管道等,也都要经过统一的文件系统来管理。

用 ls -l 命令看最前面的字符能够看到这个文件是什么类型

brw-r--r-- 1 root    root    1, 2 4月  25 11:03 bnod // 块设备文件
crw-r--r-- 1 root    root    1, 2 4月  25 11:04 cnod // 符号设备文件
drwxr-xr-x 2 wrn3552 wrn3552    6 4月  25 11:01 dir // 目录
-rw-r--r-- 1 wrn3552 wrn3552    0 4月  25 11:01 file // 普通文件
prw-r--r-- 1 root    root       0 4月  25 11:04 pipeline // 有名管道
srwxr-xr-x 1 root    root       0 4月  25 11:06 socket.sock // socket文件
lrwxrwxrwx 1 root    root       4 4月  25 11:04 softlink -> file // 软链接
-rw-r--r-- 2 wrn3552 wrn3552 0 4月  25 11:07 hardlink // 硬连接(本质也是普通文件)

Linux 文件系统设计了两个数据结构来管理这些不一样种类的文件:

  • inode(index node):索引节点
  • dentry(directory entry):目录项
inode 和 dentry

inode

inode 是用来记录文件的 metadata,所谓 metadata 在 Wikipedia 上的描述是 data of data,其实指的就是文件的各类属性,好比 inode 编号、文件大小、访问权限、修改日期、数据的位置等。

wrn3552@novadev:~/playground$ stat file
  文件:file
  大小:0               块:0          IO 块:4096   普通空文件
设备:fe21h/65057d      Inode:32828       硬连接:2
权限:(0644/-rw-r--r--)  Uid:( 3041/ wrn3552)   Gid:( 3041/ wrn3552)
最近访问:2021-04-25 11:07:59.603745534 +0800
最近更改:2021-04-25 11:07:59.603745534 +0800
最近改动:2021-04-25 11:08:04.739848692 +0800
建立时间:-

inode 和文件一一对应,它跟文件内容同样,都会被持久化存储到磁盘中。因此,inode 一样占用磁盘空间,只不过相对于文件来讲它大小固定且大小不算大。

dentry

dentry 用来记录文件的名字、inode 指针以及与其余 dentry 的关联关系。

wrn3552@novadev:~/playground$ tree
.
├── dir
│   └── file_in_dir
├── file
└── hardlink
  • 文件的名字:像 dir、file、hardlink、file_in_dir 这些名字是记录在 dentry 里的
  • inode 指针:就是指向这个文件的 inode
  • 与其余 dentry 的关联关系:其实就是每一个文件的层级关系,哪一个文件在哪一个文件下面,构成了文件系统的目录结构

不一样于 inode,dentry 是由内核维护的一个内存数据结构,因此一般也被叫作 dentry cache。

文件是如何存储在磁盘上的

图片

这里有张图解释了文件是如何存储在磁盘上的,首先,磁盘再进行文件系统格式化的时候,会分出来 3 个区:

  1. Superblock
  2. inode blocks
  3. data blocks

(其实还有 boot block,可能会包含一些 bootstrap 代码,在机器启动的时候被读到,这里忽略)其中 inode blocks 放的都是每一个文件的 inode,data blocks 里放的是每一个文件的内容数据。这里关注一下 superblock,它包含了整个文件系统的 metadata,具体有:

  1. inode/data block 总量、使用量、剩余量
  2. 文件系统的格式,属主等等各类属性

superblock 对于文件系统来讲很是重要,若是 superblock 损坏了,文件系统就挂载不了了,相应的文件也没办法读写。既然 superblock 这么重要,那确定不能只有一份,坏了就没了,它在系统中是有不少副本的,在 superblock 损坏的时候,可使用 fsck(File System Check and repair)来恢复。回到上面的那张图,能够很清晰地看到文件的各类属性和文件的数据是如何存储在磁盘上的:

  1. dentry 里包含了文件的名字、目录结构、inode 指针
  2. inode 指针指向文件特定的 inode(存在 inode blocks 里)
  3. 每一个 inode 又指向 data blocks 里具体的 logical block,这里的 logical block 存的就是文件具体的数据

这里解释一下什么是 logical block:

  1. 对于不一样存储介质的磁盘,都有最小的读写单元
  • /sys/block/sda/queue/physical_block_size
  1. HDD 叫作 sector(扇区),SSD 叫作 page(页面)
  2. 对于 hdd 来讲,每一个 sector 大小 512Bytes
  3. 对于 SSD 来讲每一个 page 大小不等(和 cell 类型有关),经典的大小是 4KB
  4. 可是 Linux 以为按照存储介质的最小读写单元来进行读写可能会有效率问题,因此支持在文件系统格式化的时候指定 block size 的大小,通常是把几个 physical_block 拼起来就成了一个 logical block
  • /sys/block/sda/queue/logical_block_size
  1. 理论上应该是 logical_block_size >= physical_block_size,可是有时候咱们会看到 physical_block_size = 4K,logical_block_size = 512B 状况,其实这是由于磁盘上作了一层 512B 的仿真(emulation)(详情可参考 512e 和 4Kn

ZFS

这里简单介绍一个普遍应用的文件系统 ZFS,一些数据库应用也会用到 ZFS,先看一张 zfs 的层级结构图:

图片

这是一张从底向上的图:

  1. 将若干物理设备 disk 组成一个虚拟设备 vdev(同时,disk 也是一种 vdev)
  2. 再将若干个虚拟设备 vdev 加到一个 zpool 里
  3. 在 zpool 的基础上建立 zfs 并挂载(zvol 能够先不看,咱们没有用到)

ZFS 的一些操做

建立 zpool

root@:~ # zpool create tank raidz /dev/ada1 /dev/ada2 /dev/ada3 raidz /dev/ada4 /dev/ada5 /dev/ada6
root@:~ # zpool list tank
NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
tank     11G   824K  11.0G        -         -     0%     0%  1.00x  ONLINE  -
root@:~ # zpool status tank
  pool: tank
 state: ONLINE
  scan: none requested
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          raidz1-0  ONLINE       0     0     0
            ada1    ONLINE       0     0     0
            ada2    ONLINE       0     0     0
            ada3    ONLINE       0     0     0
          raidz1-1  ONLINE       0     0     0
            ada4    ONLINE       0     0     0
            ada5    ONLINE       0     0     0
            ada6    ONLINE       0     0     0
  • 建立了一个名为 tank 的 zpool
  • 这里的 raidz 同 RAID5

除了 raidz 还支持其余方案:

图片

建立 zfs

root@:~ # zfs create -o mountpoint=/mnt/srev tank/srev
root@:~ # df -h tank/srev
Filesystem    Size    Used   Avail Capacity  Mounted on
tank/srev     7.1G    117K    7.1G     0%    /mnt/srev
  • 建立了一个 zfs,挂载到了 /mnt/srev
  • 这里没有指定 zfs 的 quota,建立的 zfs 大小即 zpool 大小

对 zfs 设置 quota

root@:~ # zfs set quota=1G tank/srev
root@:~ # df -h tank/srev
Filesystem    Size    Used   Avail Capacity  Mounted on
tank/srev     1.0G    118K    1.0G     0%    /mnt/srev

ZFS 特性

Pool 存储

上面的层级图和操做步骤能够看到 zfs 是基于 zpool 建立的,zpool 能够动态扩容意味着存储空间也能够动态扩容,并且能够建立多个文件系统,文件系统共享完整的 zpool 空间无需预分配。

事务文件系统

zfs 的写操做是事务的,意味着要么就没写,要么就写成功了,不会像其余文件系统那样,应用打开了文件,写入还没保存的时候断电,致使文件为空。zfs 保证写操做事务采用的是 copy on write 的方式:

图片

  • 当 block B 有修改变成 B1 的时候,普通的文件系统会直接在 block B 原地进行修改变成 B1
  • zfs 则会再另外一个地方写 B1,而后再在后面安全的时候对原来的 B 进行回收
  • 这样结果就不会出现 B 被打开而写失败的状况,大不了就是 B1 没写成功

这个特性让 zfs 在断电后不须要执行 fsck 来检查磁盘中是否存在写操做失败须要恢复的状况,大大提高了应用的可用性。

ARC 缓存

ZFS 中的 ARC(Adjustable Replacement Cache) 读缓存淘汰算法,是基于 IBM 的 ARP(Adaptive Replacement Cache) 演化而来。在一些文件系统中实现的标准 LRU 算法实际上是有缺陷的:好比复制大文件之类的线性大量 I/O 操做,致使缓存失效率猛增(大量文件只读一次,放到内存不会被再读,坐等淘汰)。

另外,缓存能够根据时间来进行优化(LRU,最近最多使用),也能够根据频率进行优化(LFU,最近最常使用),这两种方法各有优劣,可是没办法适应全部场景。

ARC 的设计就是尝试在 LRU 和 LFU 之间找到一个平衡,根据当前的 I/O workload 来调整用 LRU 多一点仍是 LFU 多一点。

ARC 定义了 4 个链表:

  1. LRU list:最近最多使用的页面,存具体数据
  2. LFU list:最近最常使用的页面,存具体数据
  3. Ghost list for LRU:最近从 LRU 表淘汰下来的页面信息,不存具体数据,只存页面信息
  4. Ghost list for LFU:最近从 LFU 表淘汰下来的页面信息,不存具体数据,只存页面信息

ARC 工做流程大体以下:

  1. LRU list 和 LFU list 填充和淘汰过程和标准算法同样
  2. 当一个页面从 LRU list 淘汰下来时,这个页面的信息会放到 LRU ghost 表中
  3. 若是这个页面一直没被再次引用到,那么这个页面的信息最终也会在 LRU ghost 表中被淘汰掉
  4. 若是这个页面在 LRU ghost 表中未被淘汰的时候,被再一次访问了,这时候会引发一次幽灵(phantom)命中
  5. phantom 命中的时候,事实上仍是要把数据从磁盘第一次放缓存
  6. 可是这时候系统知道刚刚被 LRU 表淘汰的页面又被访问到了,说明 LRU list 过小了,这时它会把 LRU list 长度加一,LFU 长度减一
  7. 对于 LFU 的过程也与上述过程相似

ZFS 参考资料

关于 ZFS 详细介绍能够参考:

磁盘类型

磁盘根据不一样的分类方式,有各类不同的类型。

磁盘的存储介质

根据磁盘的存储介质能够分两类(你们都很熟悉):

  • HDD(机械硬盘)
  • SSD(固态硬盘)

磁盘的接口

根据磁盘接口分类:

  • IDE (Integrated Drive Electronics)
  • SCSI (Small Computer System Interface)
  • SAS (Serial Attached SCSI)
  • SATA (Serial ATA)
  • ...

不一样的接口,每每分配不一样的设备名称。好比, IDE 设备会分配一个 hd 前缀的设备名,SCSI 和 SATA 设备会分配一个 sd 前缀的设备名。若是是多块同类型的磁盘,就会按照 a、b、c 等的字母顺序来编号。

Linux 对磁盘的管理

其实在 Linux 中,磁盘其实是做为一个块设备来管理的,也就是以块为单位读写数据,而且支持随机读写。每一个块设备都会被赋予两个设备号,分别是主、次设备号。主设备号用在驱动程序中,用来区分设备类型;而次设备号则是用来给多个同类设备编号。

g18-"299" on ~# ls -l /dev/sda*
brw-rw---- 1 root disk 8,  0 Apr 25 15:53 /dev/sda
brw-rw---- 1 root disk 8,  1 Apr 25 15:53 /dev/sda1
brw-rw---- 1 root disk 8, 10 Apr 25 15:53 /dev/sda10
brw-rw---- 1 root disk 8,  2 Apr 25 15:53 /dev/sda2
brw-rw---- 1 root disk 8,  5 Apr 25 15:53 /dev/sda5
brw-rw---- 1 root disk 8,  6 Apr 25 15:53 /dev/sda6
brw-rw---- 1 root disk 8,  7 Apr 25 15:53 /dev/sda7
brw-rw---- 1 root disk 8,  8 Apr 25 15:53 /dev/sda8
brw-rw---- 1 root disk 8,  9 Apr 25 15:53 /dev/sda9
  • 这些 sda 磁盘主设备号都是 8,表示它是一个 sd 类型的块设备
  • 次设备号 0-10 表示这些不一样 sd 块设备的编号

Generic Block Layer

图片

和 VFS 相似,为了对上层屏蔽不一样块设备的差别,内核在文件系统和块设备以前抽象了一个 Generic Block Layer(通用块层),有时候一些人也会把下面的 I/O 调度层并到通用块层里表述。

这两层主要作两件事:

  1. 跟 VFS 的功能相似。向上,为文件系统和应用程序,提供访问块设备的标准接口;向下,把各类异构的磁盘设备抽象为统一的块设备,并提供统一框架来管理这些设备的驱动程序
  2. 对 I/O 请求进行调度,经过从新排序、合并等方式,提升磁盘读写效率

下图是一个完整的 I/O 栈全景图:

图片

能够看到中间的 Block Layer 其实就是 Generic Block Layer,在图中能够看到 Block Layer 的 I/O 调度分为两类,分别表示单队列和多队列的调度:

  • I/O scheduler
  • blkmq

I/O 调度

老版本的内核里只支持单队列的 I/O scheduler,在 3.16 版本的内核开始支持多队列 blkmq,这里介绍几种经典的 I/O 调度策略。

单队列 I/O scheduler:

  • NOOP:事实上是个 FIFO 的队列,只作基本的请求合并
  • CFQ:Completely Fair Queueing,彻底公平调度器,给每一个进程维护一个 I/O 调度队列,按照时间片来均匀分布每一个进程 I/O 请求,
  • DeadLine:为读和写请求建立不一样的 I/O 队列,确保达到 deadline 的请求被优先处理

多队列 blkmq:

  • bfq:Budget Fair Queueing,也是公平调度器,不过不是按时间片来分配,而是按请求的扇区数量(带宽)
  • kyber:维护两个队列(同步/读、异步/写),同时严格限制发到这两个队列的请求数以保证相应时间
  • mq-deadline:多队列版本的 deadline

性能指标

通常来讲 I/O 性能指标有这几个:

  • 使用率:ioutil,指的是磁盘处理 I/O 的时间百分比,ioutil 只看有没有 I/O 请求,不看 I/O 请求的大小。ioutil 越高表示一直都有 I/O 请求,不表明磁盘没法响应新的 I/O 请求
  • IOPS:每秒的 I/O 请求数
  • 吞吐量/带宽:每秒的 I/O 请求大小,一般是 MB/s 或者 GB/s 为单位
  • 响应时间:I/O 请求发出到收到响应的时间
  • 饱和度:指的是磁盘处理 I/O 的繁忙程度。这个指标比较玄学,没有直接的数据能够表示,通常是根据平均队列请求长度或者响应时间跟基准测试的结果进行对比来估算

(在作基准测试时,还会分顺序/随机、读/写进行排列组合分别去测 IOPS 和带宽)

上面的指标除了饱和度外,其余均可以在监控系统中看到。Linux 也提供了一些命令来输出不一样维度的 I/O 状态:

  • iostat -d -x:看各个设备的 I/O 状态,数据来源 /proc/diskstats
  • pidstat -d:看近处的 I/O
  • iotop:相似 top,按 I/O 大小对进程排序