天天看点

XFS 文件系统 (一) :设计概览

文章目录

  • ​​0 前言​​
  • ​​1 设计背景​​
  • ​​2. 需要解决的问题​​
  • ​​2.1 异常恢复太慢​​
  • ​​2.2 不支持大文件系统​​
  • ​​2.3 不支持大型稀疏文件​​
  • ​​2.4 不支持大型连续文件​​
  • ​​2.5 不支持大目录​​
  • ​​2.6 不支持过多文件个数​​
  • ​​3 XFS 架构​​
  • ​​4 痛点解决​​
  • ​​4.1 Allocation Groups​​
  • ​​4.2 Manging Free Space​​
  • ​​4.3 大文件的支持​​
  • ​​5 总结​​

0 前言

虽然工作精力主要是在 存储引擎方向上,但是目前业界大多数的存储引擎都是构建在 os fs上, 所以深入理解文件系统工作原理对于存储引擎的设计也有不少的帮助。

尤其是近期工作中遇到一些 engine on ape-xfs 的性能问题,发现文件系统的知识欠缺还是比较严重的。

举个例子,on aep 也就是 pmem 上做了一个fsdax 模式的pmem namespace,像使用磁盘一样使用它 需要格式化为对应的文件系统。因为 pmem 是插在内存插槽上(和cpu 更近),且libpmem 驱动仅只支持 dax 模式的挂载,用于加速访问pmem。所以 on pmem 的文件系统 是无法使用 page-cache 的,也就是像 inode/dentry 这样的文件/目录元数据 信息是没有办法缓存到 icache/dcache的,这样,针对这一些元数据的更新就需要直接落在pmem上,像存储引擎的 wal 是需要频繁写文件,那么文件的inode 元信息(file size)的更新就会非常频繁(除非预分配),没有预分配的情况下写性能就会非常之差。

这里如果能够深入了解文件系统的基本原理,这样的问题抓个栈就是一眼的事情,不需要消耗过多的时间。

本篇 以及 后续的文章还是以技术设计 为主 进行学习,不会太深入代码。

先以 xfs 设计实现为主,毕竟centos7/8 的默认文件系统,后面再逐个梳理 与其他文件系统的实现差异,毕竟文件系统过于庞大,这里仅仅做一些初步设计的记录

1 设计背景

xfs 是大名鼎鼎的 硅谷 图形图像高性能计算公司巨头 SiliconGraphic Inc 1993 年为自己内部 高性能服务器设计的文件系统。因为现在的 不少开发者 深度参与了 xfs 的设计开发,所以才成为今天centos 的主流文件系统。

XFS 被 SGI 设计出来的目的肯定是未来解决已有文件系统在大多数场景下的问题的,SGI 内部现有的文件系统 EFS(existing file system) 因为设计,无法发挥硬件性能,导致文件系统成为 os 的 i/o 瓶颈。还有很多其他功能上的限制,主要是以下几个方面。

  1. EFS 采用的是 extent 方式管理 磁盘块的分配 以及 调度磁盘IO,无法完全发挥磁盘介质性能。
  2. 单个文件系统的大小不能超过 8G (当时的磁盘已经有T级别的了,hdd),单个文件的大小不能超过2G。
  3. 在EFS 上解决一些功能上的限制和 性能问题,因为架构设计,工作量基本接近重写了。

因为SGI 提供给用户的服务器需要解决 用户大量的视频/图像 存储需求 以及 访问性能,这一些随着用户需求的不断增加(全球化时代 上层互联网应用在高速发展,单服务器的存储体量在不断增加,存储更大性能更快是当时的服务器存储的必然趋势。。。),满足不了需求的SGI 需要开辟新疆土,所以就有了XFS。

2. 需要解决的问题

这里新设计的 XFS 需要解决的问题基本就是前面设计背景中描述的EFS的现有功能/性能问题,会对每一个问题做一个展开描述。

2.1 异常恢复太慢

EFS 是基于 BSD的 Fast File System(不太熟) 设计的,在机器异常之后启动一个守护程序重新 从磁盘 load 文件系统的元数据,达到一个一致性状态。这个过程会需要 fsck 不断的检查一个 8G 的文件系统中 上百个inode 需要花费几分钟的时间(HDD的带宽应该有百M,这么慢的话大概率还是文件系统设计的问题),随着磁盘容量得不断增加, 当达到TB 级别以及 上万的 inodes,如果还是这样 recovery 速度是不能忍受的。

这里启动 fsck check 一致性的过程应该是 检查了全量文件,后面的XFS 设计中就只会检查元数据了。

2.2 不支持大文件系统

这里也就是 EFS 不支持 管理大文件系统容量。SGI 希望文件系统能够支持 PB级别的容量管理功能,但是当时业界主流的文件系统基本都是 GB 级别,像是EFS 就仅仅支持8GB。因为EFS 管理文件系统空间的数据结构是没有办法动态扩展的,它用 32bit的 bitmap指针管理磁盘空间,32bit 的指针最多能够管理40亿的block,即使每一个block 的大小是8K ,最多也只能管理32T 的空间。

2.3 不支持大型稀疏文件

当时的业界也没有支持全量 64bit 的 稀疏文件存储。稀疏文件便于压缩,这样的文件一般是针对大视频处理之后形成的。

2.4 不支持大型连续文件

EFS支持 超大连续块的存储下读性能不友好。EFS 使用的是 bitmap 数组结构来管理文件系统中的空间分配和释放记录。想要在一段超大连续空间中读取指定的部分区域,查找性能并不会特别好。因为写入的时候在连续的磁盘空间上写入(顺序写)本身对HDD性能非常友好,但是读的时候在超大文件下的查找(即使是二分) 因为bitmap 数组结构的限制 会产生多次i/o,而如果不分配连续块,那写性能将会巨差。

2.5 不支持大目录

EFS 并不支持一个目录下 上千的文件管理机制。还是性能问题,因为大多的文件系统当时从一个目录下找一个文件是需要顺序遍历这个目录下的文件(怀疑目录项下的文件管理用的链表),还有一些使用的是hash 数据结构,这对点查性能友好,但是大范围扫描整个目录文件的时候性能也会很差。

当时的 NTFS 则使用 Btree 对目录下的文件entries 进行管理。

2.6 不支持过多文件个数

当时的EFS 理论上在一个文件系统内支持超大数量的文件,但是实际过程中并非如此。因为E FS 在创建文件提供的时候就已经分配好了足量个数的 inodes,这在文件系统并没有太多文件的情况下对磁盘空间浪费较为严重。同样也为文件系统管理如此巨量 的inode 带来负担,尤其是写入了很少的文件却需要从如此巨量的inode 中查找。

总之,因为EFS 内部架构设计和各个小组件的数据结构选型,导致当前文件系统的各种功能并不完备,存在较多问题,所以 全新的文件系统 XFS 设计迫在眉睫。

3 XFS 架构

基本架构如下

XFS 文件系统 (一) :设计概览

这个当时 (1996)年最初的XFS 基本架构。

和其他传统文件系统一样,都有一个 transaction manager 以及 volumn manager。XFS 支持了标准的 UNIX 和 POSIX 的语义,在当时的 SGI 自己的内核 IRIX 中基本使用了内核所提供的有利特性,包括buffer/page cache,dcache(directory cache) 以及 icache(当时叫vnode cache)。

整个XFS 架构被模块化为了几部分:

  • 最核心,最重要的就是 space manager。这个模块用来管理文件系统的空间释放和 inode的分配 以及 单个文件内部空间的分配。
  • IO manager。用来管理文件系统下发的i/o请求,依赖space manager 进行请求空间的分配和释放,并且需要持续追踪每一个文件的空间。
  • Directory manager。管理文件系统的 名字空间。
  • Buffer Cache。用于缓存前面的管理数据结构,将本应该存储在磁盘上的这一些结构缓存到内存中,加速访问。而且 buffer cache 是 被整个os 所有进程访问的。
  • Transaction manager。用于用户对一个文件元数据的原子更新需求。这有利于加速文件系统的crash recovery。
  • Volumn manager 主要是 XFS 自己做的屏蔽不通 disk driver的组件,能够方便对接不同的磁盘驱动,简化文件系统的实现(不需要为每一个磁盘驱动实现对应的调度接口,当然后来linux kernel 的 generic block layer 更完备得做了这个事情,只是当时SGI 是自己的服务器,自己维护的os)。

因为XFS 全新的设计,更完备的功能和更高的性能支持,其复杂度也更高,SGI 第一版本的XFS 实现已经超过50000 行 代码,而对应其内部的EFS 才12000 行代码。

这里简单汇总一下 xfs 实现过程中对b+ tree 的钟爱:

  • 追踪磁盘空闲空间的数据结构 从之前的bitmap 变更为了b+ tree。
  • 目录项下的文件管理数据结构也从之前的线性查找数据结构变更为了 b+tree.
  • B+tree 管理extent map,再由 extent map管理inodes,从而达到对整个文件系统inodes 的管理。
  • B+tree 用于追踪文件系统 inodes 的动态分配

接下来看看 XFS 实现过程中如何解决之前 EFS 的问题的。

4 痛点解决

4.1 Allocation Groups

XFS 支持全64bits 的存储管理。内部用到的所有计数变量都是 64bits 长度(block address 以及 inode nubmer)。为了充分利用 64bits 的并行性和可伸缩性,且 避免XFS 内部所有的数据结构都扩展到64bits,这里XFS 将文件系统拆分成不同的 region,也就是 AGs,当然也会考虑磁盘访问的局部性来利用AG 设计存储方式(毕竟。。。HDD的随机I/O 实在是不忍直视)。

每一个AG 管理整个文件系统的一部分容量,且不通 AG 之间可以并行操作。每一个AG 大小是 500M --> 4G 之间(现在已经支持到了16M-- 1TB 之间)。每一个AG 有自己的独立数据存储区域,在自己的边界区域内,管理自己内部的空闲空间和inodes。

AG结构 的设计目的 就是前面提到的提升 64bits 的文件系统并发访问 以及 文件系统的可伸缩性, 但是要说提升磁盘访问的局部性 这个 很少,主要是用在目录项的存储中。因为文件系统创建的文件会分布在不同的AG内部,如果创建了目录,那针对这个目录下创建的文件 inodes 和 blocks 索引都会放在这个目录对应的AG内部,利用 磁盘访问的局部性 (os 预读)加速读性能。不同的AG 内部可以通过 文件系统全局的 ag 指针/files/directories 来访问整个文件系统其他 AG 的文件。

当然AG 设计最主要的目的是为了 提升64bits 下的并发访问性能 和 空闲空间管理以及 inodes 分配的性能。因为 SGI 的EFS 都是单线程处理 block 的分配和释放。这种方式随着文件系统的规模不断增加,可能成为性能瓶颈,通过设计 AG 内部 拥有独立的数据结构,这样XFS 文件系统可以 在不同AG 互不影响的情况下 并发调度 一些 释放/分配 磁盘空间的操作。

更详细的AG 设计后面的系列文章会展开。

4.2 Manging Free Space

空间分配/释放的性能 和 可扩展性 是衡量一个好的文件系统的基础。能够高效的分配和释放空间,并且能够很好得处理空间碎片问题 对文件系统的性能至关重要。

XFS 在空间管理的数据结构上 相比于 EFS 来说做了比较大的改动,在每个AG 内部 将EFS原本的 bitmap 换成了b+tree。其中空闲空间的 start block的维护有一个单独的b+ tree,同时 length(有多少个空闲块)的维护也有一个b+tree,这样想要查找 一个空闲块的时候可以根据传入的空闲块的个数以及 起始空闲块进行,极大得提升了空闲空间的索引效率。

4.3 大文件的支持

在64bits 下支持超大的稀疏文件,也就是允许大文件内部有空洞,而这一些空洞不应该占用磁盘空间。如果按照传统的磁盘block 方式来管理一个文件所占用空间的话,那在这种场景下需要管理大量的blocks。XFS 采用了Data extent 的方式,每一个 extent 都是一段连续的分配给当前文件的block 空间,由一个个block offset组成,这样对于超大文件的空间管理就更加高效了。原本 采用block 的方式 , 可能一个文件需要 百万级别的block,现在这一些block 都可以聚合成一个大的extent统一管理,在extent 内部会尽可能保持 block 的连续性,甚至随着文件大小的不断增加,不同的extent 之间也可以合并。

5 总结