我们知道,HDFS 被设计成存储大规模的数据集,我们可以在 HDFS 上存储 TB 甚至 PB 级别的海量数据。而这些数据的元数据(比如文件由哪些块组成、这些块分别存储在哪些节点上)全部都是由 NameNode 节点维护,为了达到高效的访问,NameNode 在启动的时候会将这些元数据全部加载到内存中。而 HDFS 中的每一个文件、目录以及文件块,在 NameNode 内存都会有记录,每一条信息大约占用150字节的内存空间。由此可见,HDFS 上存在大量的小文件(这里说的小文件是指文件大小要比一个 HDFS 块大小(在 Hadoop1.x 的时候默认块大小64M,可以通过 dfs.blocksize 来设置;但是到了 Hadoop 2.x 的时候默认块大小为128MB了,可以通过 dfs.block.size 设置) 小得多的文件。)
至少会产生以下几个负面影响:
以上两个负面影响都不是我们想看见的。那么这么多的小文件一般在什么情况下产生?我在这里归纳为以下几种情况:
那么针对这些小文件,现有哪几种解决方案呢?
在本博客的《Hadoop小文件优化》文章中,翻译了 Cloudera 官方技术博客的《The Small Files Problem》文章,里面提供了两种 HDFS 小文件的解决方案。
HAR files
Hadoop Archives (HAR files)是在 Hadoop 0.18.0 版本中引入的,它的出现就是为了缓解大量小文件消耗 NameNode 内存的问题。HAR 文件是通过在 HDFS 上构建一个层次化的文件系统来工作。一个 HAR 文件是通过 hadoop 的 archive 命令来创建,而这个命令实 际上也是运行了一个 MapReduce 任务来将小文件打包成 HAR 文件。对客户端来说,使用 HAR 文件没有任何影响。所有的原始文件都可见并且可访问的(通过 har://URL)。但在 HDFS 端它内部的文件数减少了。架构如下:
从上面实现图我们可以看出,Hadoop 在进行最终文件的读取时,需要先访问索引数据,所以在效率上会比直接读取 HDFS 文件慢一些。
Sequence Files
第二种解决小文件的方法是使用 SequenceFile。这种方法使用小文件名作为 key,并且文件内容作为 value,实践中这种方式非常管用。如下图所示:
和 HAR 不同的是,这种方式还支持压缩。该方案对于小文件的存取都比较自由,不限制用户和文件的多少,但是 SequenceFile 文件不能追加写入,适用于一次性写入大量小文件的操作。
HBase
除了上面的方法,其实我们还可以将小文件存储到类似于 HBase 的 KV 数据库里面,也可以将 Key 设置为小文件的文件名,Value 设置为小文件的内容,相比使用 SequenceFile 存储小文件,使用 HBase 的时候我们可以对文件进行修改,甚至能拿到所有的历史修改版本。
以上三种方法虽然能够解决小文件的问题,但是这些方法都有局限:HAR Files 和 Sequence Files 一旦创建,之后都不支持修改,所以这是对读场景很友好的;而使用 HBase 需要引入外部系统,维护成本很高。最后,这些方法都没有从根本上解决。那么能不能从 HDFS 底层解决这个问题呢?也就是对使用方来说,我们不需要考虑写的文件大小。目前 Hadoop 社区确实有很多相应的讨论和方案设想。下面将简要进行描述。
HDFS-8998
我们都知道,一个文件在 HDFS 上对应一个或多个 Block,每个 Block 在 NameNode (INode 和 BlocksMap)中都存在一定的元数据,而且这些数据需要占用 NameNode 一定内存。所以说,如果 HDFS 中存在大量的小文件,因为这些小文件都是小于一个 Block 大小,所以这些文件占用了一个 Block;这样,海量的小文件占用了海量的 Block 。那我们能不能把这些小 Block 合并成一个大 Block?这正是 HDFS-8998 的思想。其核心实现如下:
从上面的阐述可以看出,一个 block 将包含多个文件,那么我们需要引入额外的服务来维护各个文件在 block 中的偏移量。其余的读写删操作如下:
HDFS-8286
HDFS-8998 的设计目标是直接从底层的 block 做一些修改,从而减少文件元数据的条数,以此来减少 NN 的内存消耗。而 HDFS-8286 的目标是直接从解决 NN 管理的元数据入手,将 NN 管理的元数据从保存在内存转向到保存在第三方 KV 存储系统中,以此减缓 NN 的内存使用。更进一步的讲,这种方法同时也提高了 Hadoop 集群的扩展性。
现在的 HDFS 是以层次结构的形式来管理文件和目录的,所有的文件和目录都表示为 inode 对象。为了实现层次结构,一个目录需要包含对其所有子文件的引用。而 HDFS-8286 的方案采用 KV 的形式来存储元数据。下面我们来看看它是怎么实现的,我们先来了解下两条 kv 对应的规则:
这个过程可以看到需要从 KV 存储里面进行多次检索,并进行解析,可能会在这里面出现一些性能问题。
Hadoop Ozone
Ozone 是 Hortonworks 基于 HDFS 实现的一个对象存储服务,旨在基于 HDFS 的 DataNode 存储,支持更大规模的数据对象存储,支持各种对象大小并且拥有 HDFS 的可靠性,一致性和可用性,对应的 issue 请参见 HDFS-7240。目前这个项目已经成为 Apache Hadoop 的子项目,参见 ozone。Ozone 的一大目标就是扩展 HDFS,使其支持数十亿个对象的存储。关于 Ozone 的使用文档可以参见 Apache Hadoop Ozone。