BlueStore——先进的用户态文件系统《一》

目录

为什么需要 BlueStore

IO 放大

对象遍历

其他

BlueStore 介绍

逻辑架构

设计思想

总结


引言

分布式存储系统通过将数据分散到多台机器上来充分利用多台机器的资源提高系统的存储能力,每台机器上的数据存放都需要本地的单机存储系统,它是整个分布式存储系统的基础,为其提供保障。设计高性能、高可靠的分布式存储系统离不开高效、一致、稳定、可靠的本地存储系统。

Ceph 是目前业内比较普遍使用的开源分布式存储系统,实现有多种类型的本地存储系统;在较早的版本当中,Ceph 默认使用 FileStore 作为后端存储,但是由于 FileStore 存在一些缺陷,重新设计开发了 BlueStore,并在L版本之后作为默认的后端存储。BlueStore 的一些设计思想对于设计满足分布式存储系统需求的本地存储系统具有参考意义,因此我们将对 BlueStore 的一些原理进行剖析,供读者进行参考和探讨。在这一章中,我们将要了解 BlueStore 的诞生背景,以及它的一些设计思想。

 

 

为什么需要 BlueStore

前面提到,BlueStore 的诞生背景是由于 FileStore 存在的一些缺陷,这些缺陷具体是什么?

 

IO 放大

FileStore 底层使用POSIX规范的文件系统接口,例如 xfs、ext4、btrfs,然而这类文件系统本身不支持数据或元数据的事务操作接口(btrfs 提供事务钩子的接口,但是测试过程中发现会导致系统宕机),而 Ceph 对于数据写入要求十分严格,需要满足事务的特性(ACID);为此 FileStore 实现了 FileJournal 功能,所有的事务都需要先写到 FileJournal 中,之后才会写入对应的文件中,以此来保证事务的原子性,但是这导致了数据 “双写” 的问题,造成至少一半磁盘带宽的浪费。此外 xfs、ext4、btrfs 这类文件系统本身存在一定的 IO 放大(即一次读写请求实际在低层磁盘发生的 IO 次数),再加上 FileStore 的日志双写,放大倍数成倍增加。

下图中的数据表示了以 block 大小为单位对不同文件系统进行读写,在不同场景下的读写放大及空间放大情况。我们以 ext4 文件系统说明下个参数的含义。在对文件进行 Overwrite 时,即将数据覆盖写入到文件中,除了写入数据外,还涉及到日志的写入(其中日志写入两次,一次记录更改的 inode,一次为 commit 记录,具体可参考)、文件 inode 的更改,每次写的最小单位是block,因此最终相当于写入次数以及空间放大了四倍;而在进行 Append 写入时,由于需要新分配空间,因此相对于 Overwrite 增加了 bitmap 的更改以及 superblock 的更改( superblock 记录总的空间分配情况),写放大和空间放大均为六倍。读文件时,在没有命中任何缓存的情况下,需要读大量元数据,例如:目录、文件 inode、superblock 等,最终读放大为六倍;而如果是在顺序读的情况下,像 superblock、bitmap、目录等这些元数据都缓存在内存中,只需读取文件 inode 和文件数据。 

同理,其他文件系统由于不同的结构和设计原理,其 IO 放大和空间放大系数也各不相同。下面这个表展示了不同文件系统在读写文件时产生的读、写和空间放大程度

 

 

对象遍历

Ceph 的数据被划分为 object 存放,object 以 32 位的 hash 值进行标识,ceph 在进行 scrubbing、backfill 或者 recovery 时都需要根据 hash 值遍历这些 object;POSIX 文件系统不提供有序的文件遍历接口,为此 FileStore 根据文件的数量和 hash 的前缀将 object 划分到不同的子目录,其原则如下:

  • 当目录下的文件个数 >100 个时,拆分子目录;目录名以文件名的 hash 前缀为依据(拆分一级目录时,以 hash 第一位为拆分依据,二级目录以第二位 hash 为拆分依据,依次类推)。
  • 当所有子目录下的文件个数 <50 个时,将合并到上级目录。

因此 FileStore 在使用过程中需要不断合并拆分目录结构;这种方式将文件按照前缀放到不同目录,但对于同一目录中的文件依然无法很好排序,因此需要将目录中的所有文件读到内存进行排序,这样在一定程度上增加了 CPU 开销。

 

其他

  • FileStore 由于设计的较早,无法支持当前较新的存储技术,例如使用 spdk 技术读写 NVMe 盘。
  • 数据和元数据分离不彻底。
  • 流控机制不完整导致 IOPS 和带宽抖动(FileStore 自身无法控制本地文件系统的刷盘行为)。
  • 频繁 syncfs 系统调用导致 CPU 利用率居高不下。

sync() causes all buffered modifications to file metadata and  data  to be written to the underlying filesystems.

syncfs()  is like sync(), but synchronizes just the filesystem containing file referred to by the open file descriptor fd.

sync() 将导致所有对文件元数据和数据的缓冲修改写入底层文件系统。

syncfs() 类似于 sync(),但只同步包含打开文件描述符 fd 引用的文件的文件系统。

 

 

BlueStore 介绍

首先看下 BlueStore 设计之初的一些需求:

  • 对全 SSD 及全 NVMe SSD 闪存适配
  • 绕过本地文件系统层,直接管理裸设备,缩短 IO 路径
  • 严格分离元数据和数据,提高索引效率
  • 使用 KV 索引,解决文件系统目录结构遍历效率低的问题
  • 支持多种设备类型
  • 解决日志“双写”问题
  • 期望带来至少 2 倍的写性能提升和同等读性能
  • 增加数据校验及数据压缩等功能

 

逻辑架构

BlueStore 的逻辑架构如上图所示,模块的划分都还比较清晰,我们来看下各模块的作用:

RocksDB:RocksDB 是 FaceBook 基于 leveldb 开发的一款 kv 数据库,BlueStore 将元数据全部存放至 RocksDB 中,这些元数据包括存储预写式日志、数据对象元数据、Ceph 的 omap 数据信息、以及分配器的元数据 。

BlueRocksEnv:这是 RocksDB 与 BlueFS 交互的接口;RocksDB 提供了文件操作的接口 EnvWrapper,用户可以通过继承实现该接口来自定义底层的读写操作,BlueRocksEnv 就是继承自 EnvWrapper 实现对 BlueFS 的读写。

BlueFS:BlueFS 是 BlueStore 针对 RocksDB 开发的轻量级文件系统,用于存放 RocksDB 产生的 .sst 和 .log 等文件。

BlockDecive:BlueStore 抛弃了传统的 ext4、xfs 文件系统,使用直接管理裸盘的方式;BlueStore 支持同时使用多种不同类型的设备,在逻辑上 BlueStore 将存储空间划分为三层:慢速(Slow)空间、高速(DB)空间、超高速(WAL)空间,不同的空间可以指定使用不同的设备类型,当然也可使用同一块设备,具体我们会在后面的文章进行说明。

Allocator:负责裸设备的空间管理,只在内存做标记,目前支持 StupidAllocator 和 BitmapAllocator 两种分配器,Stupid 基于 extent 的方式实现 。

 

 

设计思想

在设计分布式文件系统的本地存储时,我们必须考虑数据的一致性和可靠性。在数据写入的过程中,由于可能存在异常掉电、进程崩溃等突发情况,导致数据还未全部写入成功便结束。虽然硬盘本身可以保证在扇区级别写入的原子性,但是一般文件系统的一个写请求通常包含多个扇区的数据和元数据更新,无法做到原子写。

常用的解决办法是引入日志系统,数据写入磁盘之前先写到日志系统,然后再将数据落盘;日志写入成功后,即便写数据时出现异常,也可以通过日志回放重新写入这部分数据;如果写日志的过程中出现异常,则直接放弃这部分日志,视为写入失败即可,以此保证原子写入。但是这种方式导致每份数据都需要在磁盘上写入两次,严重降低了数据的写入效率。

另一种方式则是采用 ROW(Redirect on write)的方式,即数据需要覆盖写入时,将数据写到新的位置,然后更新元数据索引,这种方式由于不存在覆盖写,只需保证元数据更新的原子性即可。对于对齐的覆盖写入时,这种方式没有问题,但是如果是非对齐的覆盖写呢?

我们举个例子:某文件的逻辑空间 [0,4096) 区间的数据在磁盘上的物理映射地址为 [0, 4096),磁盘的块(即磁盘读写的最小单元)大小为 4096 ;如果要覆盖写文件 [0,4096) 区间的数据,那使用 ROW 的方式没有问题,重新再磁盘上分配一个新的块写入,然后更新元数据中的映射关系即可;但是如果写文件 [512,4096) 区域,也就是非对齐的覆盖写时,新分配的块中只有部分数据,旧的物理空间中仍有部分数据有效,这样元数据中需要维护两份索引,而且在读取文件的该块数据时,需要从多块磁盘块中读取数据,如果多次进行非对齐覆盖写,这种问题将更严重。

解决这种问题办法是使用 RMW(Read Modify Write)的方法,即在发生非对齐覆盖写时,先读取旧的数据,更新的数据合并后,对齐写入到磁盘中,从而减少元数据、提高读性能,但这种方式也存在一种缺点,写数据时需要先读数据,存在一定的性能损耗。

分析完 ROW 的方式后,读者是否会有疑问,每次写入都放到新的位置,那么文件在磁盘中的物理连续性岂不是无法保证?的确,在传统的文件系统设计时,都是面向 HDD 盘,这种类型的盘在读写时会有磁头寻道的时间,对于非连续的物理空间读写,性能极差,在设计时会尽可能考虑数据存放的连续性,因此很少会采用 ROW 的方式。但是随着 SSD 盘的逐渐普及,随机读写的性能不再成为主要的性能关注点,越来越多的存储系统开始采用全闪存的磁盘阵列,相信 ROW 的方式会成为更加主流的方式。

 

 

总结

BlueStore 的设计考虑了 FileStore 中存在的一些硬伤,抛弃了传统的文件系统直接管理裸设备,缩短了 IO 路径,同时采用 ROW 的方式,避免了日志双写的问题,在写入性能上有了极大的提高。

通过分析 BlueStore 的基本结构、考虑的问题以及设计思想,我们对于 BlueStore 有了大概的了解;BlueStore 在设计时有考虑到未来存储的应用环境,是一种比较先进的本地文件系统,但也不可避免存在一些缺陷,例如较为复杂的元数据结构和 IO 逻辑,在大量小 IO 下可能存在的 double write 问题,较大的元数据内存占用等(当然有些问题在 Ceph 的使用场景下可能不存在,但是我们如果希望借鉴 BlueStore 来设计本地文件系统就不得不考虑这些问题)。

在后续的文章中,我们将继续深入剖析各个模块的设计原理和流程,也会对 BlueStore 测试过程中发现的一些问题展开讨论。

 

参考资料

https://ceph.com/community/new-luminous-bluestore/

Jayashree Mohan, Rohan Kadekodi, Vijay Chidambaram. Analyzing IO Amplification in Linux File Systems. arXiv:1707.08514v1 [cs.OS] 26 Jul 2017 .

谢型果等. Ceph设计原理与实现[M]. 北京:机械工业出版社,2017.12.

 

原文链接:知乎链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值