本篇是一篇译文,主要介绍下FSImage改进为protobuf的设计原理,原文地址

背景

为了能从故障中快速恢复,HDFS会周期性的将内存中的namespace在磁盘上的文件系统中进行持久化。FSImage的性能和扩展性在保证数据的耐用性和可用性上至关重要。随着越来越多的高级功能被引入HDFS中,FSImage已经从一个简单的blob演变为了一个需要存储在磁盘的复杂的数据结构。FSImage有3个方面需要改进:

  • 效率
    在生成环境中,我们观察到持久化到磁盘的FSImage大小能达到二三十GB。因为加载和停机时保存FSImage是NameNode的关键环节,其效率对确保NameNode操作的可用性非常重要。
  • 兼容性
    FSImage将随着HDFS的各种功能的不断发展和演变。FSImage的实现将能够被各种不同的版本所使用,这在某些功能下非常重要,比如滚动升级。
  • 互通性
    FSImage能被第三方工具访问的需求越来越强烈了。例如某个工具可以通过扫描FSImage收集一些统计信息。因此FSImage应该提供一个使第三方工具能与其进行交互的清晰规范。

当前FSImage模块有大约2500行代码,但是现在的设计与实现并不能解决上面的需求。首先,对于兼容性而言,当前代码中的一些实现并不理想。例如有些属性比如inode IDs和block IDs被编码为了固定长度的整数而不是变长整数。

其次,代码依赖与分支上的不同布局版本以保持向后兼容。在代码更新时在修改和测试上需要耗费很大的精力。然而,还不支持向前兼容,这使NameNode降级变的很困难

再次,FSImage的格式仅由代码的具体实现而决定。如果没有正式的FSImage规范,开发人员构建第三方工具时,只能通过offline image viewer来构建,这种方式时低效的而且还容易出错。

为了解决上面的挑战,我们提议使用Google的Protocol Buffers协议来定义一种新的FSImage格式。Protocol Buffers是可扩展的,与语言无关的,用于序列化结构数据。Protobuf能解决上面的挑战。首先,Protocol使用一个编译器为序列化和反序列化生成高效的代码。其次,它还支持向前和向后兼容。序列化后的每个属性都包含一个唯一的标签,因此反序列化的代码可以同时处理新加的字段和缺失的字段。最后,Protocol Buffers的源码直接定义了FSImage的格式。第三方工具仅根据protocol文件就能知道FSImage的格式。

HDFS-5698是关于FSImage新格式的issues。下面的内容介绍了设计的细节,新格式的一些实验参数,未来的方向和结论。

设计

下图是新FSImage格式的EBNF语法描述。整个FSImage包括4部分,分别为MAGIC、SECTION、FileSummary和FileSummaryLength,其中SECTION包含N个。
FSImage格式

FSImage将数据分成不同的部分。每部分在数据中都是相互独立的。开发人员可以添加新的部分来扩展FSImage。

FileSummary模块包含4个属性,分别为ondiskVersion、layoutVersion、codec和sections,其中sections中记录了每个section的名字、长度和偏移量。所有的section可以使用指定的codec对其进行压缩。每个section都是单独压缩的,而且section中记录的长度和偏移量都是压缩之后的,因此当读取的时候可以直接通过响应的偏移量而直接定位到某个section。

当解析FSImage时,先从文件的末尾读取4bytes,读取的4bytes数据记录了FileSummary模块的长度,通过解析FileSummary模块能得到所有section的位置信息,这样设计允许写入时按顺序保留FSImage。

序列化inode

FSImage将inode序列化为两种section。INodeSection记录了每个inode(包括INodeFile和INodeDirectory)的信息,INodeDirectorySection记录了每个目录的子inodes。这种将inodes当作图而不是树的设计模式有两个优点,第一与实际的图更加匹配,因此当快照存在时,inode不用再形成树。不同的目录能通过INodeReference对象指向同一个inode。第二它将结构扁平化,从而为并行性提供了机会。每个inode都独立的存储在FSImage中,因此可以并行的解析和构建不同的inodes。

在protobuf的规范中,INodeSection并不包含inode列表,而是在INodeSection随后的protobuf消息中隐式的指定了inode列表。这样做的原因时Protocol Buffers在生成代码的时候需要将所有的属性加载到内存,然而inodes列表可能太大而不能加载到内存。文中的这种设计允许FSImage以递增的方式解析。

令人惊讶的是,我们的初始分析结果表明序列化权限是代码中的瓶颈。权限是一个元组,包含用户名,组名和表示权限位的16位整数。简单的解决方案是直接序列化两个名称和16位整数。 但是,我们发现FSImage中的名称是高度冗余的,反序列化为Java的String对象需要大量的CPU时间。为了优化此用例,FSImage将名称映射为24位ID,以便可以使用64位整数表示权限。

实验

为了评估新FSImage的性能,我们比较了新旧格式的FSImage文件的大小,和持久化和加载FSImage所需要的时间。我们将生产中使用的FSImage缩放到五种不同的尺寸,以评估具有各种尺度集群的性能。所有的测试都运行在相同的机器上。

图2比较了新旧FSImage的大小。FSImage都是未压缩的。由图中所知新格式的FSImage能够节省6-13%的磁盘空间。
图2

图3展示了新旧FSImage加载和持久化的处理时间,也都是未压缩的。新FSImage有着较好的写性能。
图3

新FSImage读比旧FSImage所耗的时间长,大约为1.5x-2.3x。分析结果表明,解析字节和构造为protocol buffers生成的对象占用大部分时间。 旧格式的代码读取数据并直接构造HDFS中使用的对象,这不会受到这种类型的开销的影响。

未来工作

并行化

新FSImage的设计格式为并行加载提供了机会。新的FSImage单独存储每个inode,因此可以并行的解析数据和重构inode。同样,新FSImage也可以并行的填充不同目录的子节点。

重建BlockMap占用CPU时间的大约为20%。在新的FSImage格式下,可以将BlockMap的工作负载转移到单独的线程中,从而隐藏延迟。

直接构建对象

新的FSImage需要首先通过Protocol Buffers生成的对象,然后根据生成的对象构造HDFS中使用的对象。实验结果显示构造生成的对象需要大量时间。 可以手动解析Protocol Buffers并直接构造对象,从而消除了开销。

结论

FSImage的性能和可靠性对于提供HDFS的耐用性和可用性非常重要。本文描述了一种基于Protocol Buffers的新FSImage格式。新格式满足了FSImage的效率,兼容性和互操作性要求。 我们希望新格式可以促进HDFS的创新。