命名空间 键值 存储_Alluxio中可扩展的元数据服务:存储数十亿个文件

了解如何使用RocksDB作为嵌入式持久键值存储来编码和存储具有高性能的文件系统inode树。

Alluxio提供统一的命名空间,您可以在其中安装多个不同的存储系统并通过相同的API访问它们。为了服务文件系统请求操作此命名空间中的所有文件和目录,Alluxio主服务器必须以所有已安装系统组合的比例处理文件系统元数据。我们正在编写几个工程博客,描述Alluxio master的设计和实现,以解决这一可扩展性挑战。这是第一篇专注于元数据存储和服务的文章,特别是如何使用RocksDB作为嵌入式持久键值存储来编码和存储具有高性能的文件系统inode树。

命名空间 键值 存储_Alluxio中可扩展的元数据服务:存储数十亿个文件_第1张图片

概观

Alluxio将来自单个活动主服务器的元数据作为主要和可能多个备用主服务器提供高可用性。主服务器处理所有元数据请求,并使用预写日志记录所有更改,以便我们可以从崩溃中恢复。通常将日志写入HDFS等共享存储,以实现持久性和可用性。备用主机读取预写日志以使其自己的状态保持最新。如果主要主设备死亡,其中一个备用设备可以快速接管它。

在Alluxio 1.x中,所有文件系统元数据都存储在Alluxio主节点的堆上,并以类似于Java HashMap的数据结构进行组织。虽然实现相对简单明了,但实际上这也将文件的最大数量限制在2亿左右。在堆上存储更多文件会导致垃圾收集失控。对于Alluxio 2,我们的目标是扩展到10亿个文件。

这里有一些选择。一种方法是跨多个主服务器对元数据进行分区,让主动主服务器查找相关的元数据。另一种方法是让多个活动主服务器服务于命名空间的不同部分。在Alluxio 2.0中,我们从第三个选项开始:将所有元数据保留在与主节点相同的节点上,但将一部分命名空间存储在磁盘上。这使架构简单,并最大限度地减少了为请求提供服务所需的网络跃点数。

Inode的磁盘表示

Alluxio支持读密集型和写密集型工作负载,因此我们需要一个适用于两者的磁盘存储系统。我们之所以选择RocksDB键值存储,是因为它将高效的磁盘友好写入与高效的点查找和范围扫描相结合。RocksDB是可嵌入的,并提供键值API,其中所有键和值都是字节数组。

我们将文件系统树表示为inode和edge的图形。Inode包含有关文件或目录的元数据,并由64位inode id键入。边缘定义文件系统树中的父子关系。关键是父级的64位inode id与子级名称连接,值是子级的inode id。此表示允许有效的创建,删除,重命名和目录列表。

Inode表

命名空间 键值 存储_Alluxio中可扩展的元数据服务:存储数十亿个文件_第2张图片

边缘表

命名空间 键值 存储_Alluxio中可扩展的元数据服务:存储数十亿个文件_第3张图片

要在路径“/ dir / file”中查找inode的元数据,我们执行以下操作:

  1. 在inode表中查找“/”并找到id 0
  2. 在边缘表中查找“0,dir”并找到id 1
  3. 在边缘表中查找“1,file”并找到id 2
  4. 在inode表中查找2并找到inode 2。

要列出子项“/”,我们对从0开始的所有边进行范围扫描。

有用的RocksDB功能

前缀迭代器

在定义边缘表时,我们告诉RocksDB我们经常对每个密钥中的8字节前缀(inode id)执行范围扫描。然后,RocksDB将为每个数据文件中的前缀维护布隆过滤器,以便范围扫描可以避免搜索不包含密钥的文件。

列族

RocksDB允许您将数据库分区为多个列族。这便于将inode和edge表存储在同一个数据库实例中。

性能优化

内存中的Inode缓存

当我们从RocksDB中读取inode时,我们将它们反序列化为Java Inode对象。将inode存储回RocksDB时,我们需要序列化java对象。这种序列化和反序列化很昂贵。在使用RocksDB作为后端的元数据基准测试中,我们观察到这种序列化和反序列化使用了大约50%的CPU,与原始的仅堆存储相比,显着降低了元数据性能。

为了减轻开销,我们引入了一个堆栈缓存来存储最近访问的Java Inode对象。缓存提供对常用inode的快速访问。它还通过缓冲内存中的更改来加速写入,然后将它们异步刷新到RocksDB。当工作集适合缓存时,元数据性能与仅堆存储相当。我们可以减轻序列化开销的另一种方法是使用平缓冲突。平缓冲区允许我们访问inode数据而无需反序列化。但是,为了获得平坦缓冲区的好处,我们需要重新编写所有主代码以使用平缓冲区而不是Java Inode。

锁定

以前,inode树中的每个inode都有一个嵌入式读写锁,以支持细粒度的并发控制。对于存储在RocksDB中的inode,这种方法不再有效。我们创建了一个锁管理器,它存储了从inode id到最近使用的inode的锁的映射,而不是将我们的锁嵌入inode中。根据需要创建和缓存锁,具有LRU样式的驱逐。

我们最初的实现使用了Guava的CacheBuilder,但我们发现它在处理每秒数万次操作时消耗了太多的CPU。我们通过在标准库ConcurrentHashMap之上构建我们自己的基本缓存来修复瓶颈。

检查点

当Alluxio master启动时,它会通过读取最新的元数据检查点以及在该检查点之后写入的所有编辑日志来恢复其先前的状态。以前,检查点文件和编辑日志使用相同的格式:协议缓冲区编辑条目列表。这些条目将一次读取一个并应用于主状态。主设备每秒可以处理大约100k的这些条目。这需要几分钟的数千万个文件的规模,以及10亿个文件的规模。为了缩短启动时间,我们利用了RocksDB备份引擎。要创建检查点,我们使用备份引擎创建备份,然后创建备份的压缩tar存档。要恢复,我们会对存档进行充气,然后使用RocksDB备份引擎从备份中恢复。使用RocksDB备份,

摘要

扩大元数据服务的存储容量对于Alluxio至关重要。使用RocksDB并利用磁盘作为存储资源并结合一组性能优化,我们能够以相当的性能存储10亿个文件。

你可能感兴趣的:(命名空间,键值,存储)