[TOC]
在第一篇文章中,我讨论了什么构成了一个小文件,以及为什么 Hadoop 存在小文件问题。我将一个小文件定义为小于 Hadoop 块大小 75%的任何文件,并解释说由于 NameNode 内存使用和 MapReduce 性能,Hadoop 更喜欢较少的较大文件。在这篇文章中,当小文件真正不可避免时,我将讨论这些挑战的解决方案。
解决 NameNode 内存问题
正如之前的文章中所讨论的,Hadoop 中每个块的元数据必须存储在 NameNode 的内存中。这导致实际限制 Hadoop 中可以存储的对象数量,并且还会影响启动时间和网络带宽。有两种解决方案,减少 Hadoop 集群中的对象数量,或以某种方式使 NameNode 更多地使用内存 – 但不会导致过多的启动时间。解决此内存问题的最常用方法涉及 Hadoop 存档(HAR)文件和联合 NameNodes。
Hadoop 存档文件
Hadoop 归档文件通过将许多小文件打包到更大的 HAR 文件中来缓解 NameNode 内存问题,类似于 Linux 上的 TAR 文件。这导致 NameNode 保留单个 HAR 文件的知识,而不是数十个或数百个小文件。可以使用 har:// 前缀而不是 hdfs:// 来访问 HAR 文件中的文件。HAR 文件是从 HDFS 中存在的文件创建的。因此,HAR 文件可以合并摄取的数据以及通过正常的 MapReduce 处理创建数据。可以独立于用于创建小文件的技术来使用 HAR 文件。除了 HDFS 之外没有共同的依赖。
虽然 HAR 文件减少了许多小文件的 NameNode 内存占用,但访问和处理 HAR 文件内容的效率可能会降低。HAR 文件仍然随机存储在磁盘上,并且读取 HAR 内的文件需要两个索引访问 – 一个用于 NameNode 以找到 HAR 文件本身,一个用于在 HAR 内查找小文件的位置。在 HAR 中读取文件实际上可能比读取本机存储在 HDFS 上的相同文件慢。MapReduce 作业会影响此性能问题,因为它们仍将在 HAR 中的每个文件中启动一个 map 任务。
最后,你有一个 HAR 文件可以解决 NameNode 内存问题,但可能会恶化处理性能。如果您的小文件主要用于存档目的,并且不经常访问,那么 HAR 文件是一个很好的解决方案。如果小文件是正常处理流程的一部分,您可能需要重新考虑您的设计。
Federated NameNodes
Federated NameNodes 允许您在群集中拥有多个 NameNode,每个 NameNode 都存储对象元数据的子集。这消除了将所有对象元数据存储在单个机器上的需要,从而为内存使用提供了更多的扩展。从表面上看,用这种技术解决小文件内存问题很有吸引力,但是稍微想一想你会很快意识到这些局限性。
Federated NameNodes 隔离对象元数据 – 只有一个 NameNode 知道任何特定对象。这意味着要获取文件,您必须知道要使用哪个 NameNode。如果您的群集包含多个租户或孤立的应用程序,那么 Federated NameNode 很自然 – 您可以通过租户或应用程序隔离对象元数据。但是,如果要在群集中的所有应用程序之间共享数据,则此方法并不理想。
由于 Federated 实际上不会更改群集中的对象或块的数量,因此它无法解决 MapReduce 性能问题。相反,Federated 为您的 Hadoop 安装和管理增加了重要且通常不必要的复杂性。当用于解决小文件问题时,通常更多的是隐藏小文件问题的机制。
解决 MapReduce 性能问题
MapReduce 性能问题是由随机磁盘 IO 和启动 / 管理太多 map 任务的组合引起的。解决方案似乎很明显 – 拥有更少,更大的文件或启动更少的 map 任务; 然而,这说起来容易做起来难。一些最常见的解决方案包括:
更改摄取过程 / 间隔
批处理文件合并
序列文件
HBase
S3DistCp(如果使用 Amazon EMR)
使用 CombineFileInputFormat
Hive 配置
使用 Hadoop 的附加功能
更改摄取过程 / 间隔
摆脱小文件的最简单方法就是不首先生成它们。如果源系统生成数千个复制到 Hadoop 的小文件,请调查更改源系统以生成一些大文件,或者在摄取到 HDFS 时可能连接文件。如果您每小时仅摄取 10 MB 数据,请确定是否每天只能摄取一次。您将创建 1x240MB 文件而不是 24x10MB 文件。但是,您可能无法控制创建文件的源系统或业务需求要求您以间隔频率接收数据,以便小文件不可避免。如果小文件确实是不可避免的,那么应该考虑其他解决方案。
批处理文件合并
当小文件不可避免时,文件合并是最常见的解决方案。使用此选项,您可以定期运行一个简单的合并 MapReduce 作业来读取文件夹中的所有小文件,并将它们重写为更少的大文件。如果文件夹中有 1000 个文件,并且 MapReduce 作业仅指定 5 个文件,则 1000 个输入文件将合并为 5 个输出文件。接下来是一些简单的 HDFS 文件 / 文件夹操作,您将内存占用减少了 200:1,并且可能提高了对同一数据的未来 MapReduce 处理的性能。
这可以在 Pig,load 和 store 语句中实现。例如,如果合并文本文件:
在 Hive 或 Java MapReduce 中实现这一点也同样容易。这些 MapReduce 作业在执行时显然需要集群资源,并且通常在非工作时间进行调度。但是,应该足够频繁地运行它们,这样小文件的性能影响就不会变得太大。通常在这些作业中内置额外的逻辑,以便只合并文件夹中对性能有显著影响的文件。在一个仅包含三个文件的文件夹中合并文件的性能优势不如在一个包含 500 个小文件的文件夹中合并文件。
检查文件夹以确定应合并哪些文件夹可以通过多种方式完成。例如,Pentaho 数据集成作业可用于迭代 HDFS 中的一组文件夹,找到满足最小合并要求的文件夹。还有一个专门为此任务设计的预编写应用程序名为 File Crush,这是一个由 Edward Capriolo 编写的开源项目。File Crush 不受专业支持,因此不保证它将继续与未来版本的 Hadoop 一起使用。
批处理文件合并不会保留原始文件名。如果拥有原始文件名对于处理或了解数据来源非常重要,则批处理文件合并将不起作用。但是,大多数 HDFS 设计在文件夹级别而不是在每个文件中嵌入命名语义。采用这种做法会将文件名依赖性作为一个问题删除。
序列文件
当需要维护原始文件名时,一种非常常见的方法是使用 Sequence 文件。在此解决方案中,文件名作为密钥存储在序列文件中,文件内容作为值存储。下表给出了如何将小文件存储在序列文件中的示例:
如果您有 10,000 个小文件,则您的序列文件将包含 10,000 个密钥,每个文件一个。序列文件支持块压缩,并且是可拆分的,这意味着 MapReduce 作业每个 128MB 块只能启动一个 map 任务,而不是每个小文件一个 map 任务。当您需要维护输入文件名,并且同时摄取数百或数千个小文件时,这非常有效。
但是,如果您一次只提取少量小文件,则序列文件也不能正常工作,因为 Hadoop 文件是不可变的,无法追加。三个 10MB 文件将产生 30MB 的序列文件,根据我们的定义,这仍然是一个小文件。另一个挑战是检索序列文件中的文件名列表需要处理整个文件。
此外,Hive 在此结构中与序列文件不兼容。Hive 将值中的所有数据视为单行。使用 Hive 查询此数据并不容易,因为文件的整个内容将是 Hive 中的单行。最后,您创建的 Hive 表将无法访问序列文件密钥,文件名,并且只能访问值,即文件的内容。可以编写自定义 Hive serde 来解决这些挑战,但这是一个超越 Hadoop 本机功能的高级功能。
结论
我们讨论了使用 Hadoop Archive(HAR)文件来最小化 NameNode 内存使用的权衡。我们讨论并驳回了使用 Federated NameNodes 作为小文件问题的灵丹妙药。并且,我们为小文件整合引入了一些常用的解决方案 – 这些解决方案可以提高 NameNode 内存使用率和 MapReduce 性能。