Hadoop–小文件存储处理
本篇文章项目是Cloudera论坛中无意中看到的,虽然原文写于2009年,但是当前开来Hadoop的小文件存储方案并没有很好的解决方案,所以感觉作者写的很好,也很详细,于是就抽空翻译了。本次翻译主要针对对Hadoop感兴趣和工作中使用到Hadoop的开发人员提供有价值的中文资料,希望能够对大家的工作和学习有所帮助。
由于我英语水平有限,部分翻译虽能了解其大意,但是却没法很好的表达出来,所以有些地方翻译的不是很好。同时也由于能力见识有限,翻译过程中难免出现个人的主观或者客观原因导致与原文档有差异。在此,我还是建议有能力的童鞋能够自己去原文看看,同时如果大家发现更好的关于Hadoop小文件处理的文件,也建议大家提出来与大家一同分享。
小文件问题
在Hadoop中小文件是一个大问题 — 或者说, 至少, 他们在用户的讨论区域是比较热门的话题. 在这篇文章中我将直面这个问题, 并提出一些常见的解决方案.
在HDFS中的小文件问题
这里讨论的小文件指的是那些在HDFS中小于HDFS块大小(默认是64M)的文件. 如果你存储了很多这种小文件, 或者你有很多这种小文件 (如果你并没有使用Hadoop), 这里讨论的问题是Hadoop不能处理大量的这种文件.
每一个文件, 目录和块在HDFS中代表一个对象存储在namenode的内存中, 它们每一个占用150 字节, 根据经验. 所以1000万个文件,每一个使用一个块, 将大约需要3GB的内存. 远远超过当前的硬件处理水平. 当然10亿个文件直接没法处理.
此外, HDFS是没有能力去有效地访问小文件:它主要是专为串行流式访问的大文件设计的. 阅读小文件通常会导致从一个DataNode到DataNode检索每个小文件, 所有的这些访问数据效率都是很低下的.
在MapReduce中小文件问题
Map任务每次能处理一个块的输入 (使用的是默认的 FileInputFormat
). 如果文件非常小而且还很多, 导致每次Map任务产生非常小的输入, 同时这里需要大量的map任务, 这样增加了很多额外的开销. 比较一个1GB 文件分成16 64MB 块, 10,000 或者 100KB 文件. 10,000文件每个文件使用一个map任务, 这个任务比一个相等的大文件要慢10或者上百倍.
这里有一些特征能够减轻额外的开销:多个任务使用一个JVM, 从而避免了一些JVM启动开销 (看mapred.job.reuse.jvm.num.tasks
属性), 和MultiFileInputSplit
可以运行多个以上的分割任务.
小文件怎么产生的?
这里至少有两个场景
- 这些文件是一个更大的逻辑文件. 由于HDFS最近才支持追加,一个非常通用的保存无界文件(例如日志文件)的模式是写在HDFS块.
- 有的文件一直比较小. 想象一个大的图像库. 每一个文件是一个不同的文件, 没有自然的方法把它们组合成一个更大的文件.
这两者需要不同的解决方案. 对于第一个实例, 文件是有一个个记录组成, 这个问题可以通过调用 HDFS’s sync()
方法来避免 (这个是追加, 深入了解可以看 this discussion) 每一个如此频繁地写大文件. 另外,你可以通过写一个程序来把小文件连接在一起 (查看 Nathan Marz’s post 工具能够实现这种功能).
对于第二种情况,通过一些方式来对文件进行分组操作. Hadoop提供了几种方案.
HAR files
Hadoop Archives (HAR 文件)被引入到HDFS在0.18.0版本中为了减轻存放许多文件在namenode内存中的问题. HAR 文件的工作原理是在HDFS中建立一个分层的文件系统. 一个 HAR文件创建通过使用hadoop archive
命令,他通过运行一个MapReduce任务把HDFS中的小文件进行打包. 对于客户端使用HAR文件系统没有改变: 所有的原始文件是可见的和可访问的 (通过使用 har:// URL). 然而, 在HDFS上的文件数量并没有减少.
在HDFS上读文件比HAR上面效率要高一些, 事实上,可能会比较慢因为每个HAR文件访问需要两个索引文件的读取以及数据文件的读取(见下图). 虽然HAR文件能够作为MapReduce的输入文件 , HAR并没有准许在进行Map操作的时候把所有的文件当着一个块来操作. 它应该是提供一个输入的格式能够在HARs提升改进的地方, 实际上它并没有. 请注意 MultiFileInputSplit, 即使在 HADOOP-4565 通过选择本地文件分割来提高, 将也需要查询每一个小的文件. 通过与SequenceFile文件性能比较是很有趣的, 所以说.现在HARs最常使用的是来处理文档.
Sequence Files
通常回答关于“小文件问题” 是: 使用SequenceFile. SequenceFile文件的设计思想是使用文件名作为key文件内容作为值. 这在实践中很好. 回到10,000 100KB 文件问题, 你可以写一个程序把他们放到一个单独的SequenceFile文件里面, 并且你可以通过串流的形势对其进行访问(直接或者通过MapReduce) 在SequenceFile文件上进行操作. 更神奇的地方是. SequenceFiles文件能够被可拆分, 所以MapReduce可以打破成块,每一块独立操作. 它们也支持压缩, 不像HARs. 块压缩是大多数情况下最好的选择, 由于压缩了几个记录块(相当于每个记录).
它可以将现有的数据转化成为SequenceFiles. 然而,完全有可能在并行创建一系列sequencefiles. (Stuart Sierra 已经写了一篇很有用的关于 post 转换tar文件到SequenceFile文件 — 这种工具是很有益的, 看到更多的人会很好).最好的设计在写入数据的时候直接写入到SequenceFiles文件里面, 如果可能,而不是写小文件作为中间步骤.
不像HAR文件, SequenceFile文件没法罗列出所有的keys,简短的阅读需要获取通过整个文件. (MapFiles文件很像一个已经对key进行排序的 SequenceFiles 文件, 维护部分索引, 所以他们不能罗列所有它们的keys — 如图所示.)
SequenceFile文件相当于Java的核心. TFile文件被设计成跨平台的, 是一个可以替代的 SequenceFile, 但它尚未提供.
HBase
如果你产生了很多小文件, 然而, 根据访问模式, 不同类型的存储可能更合适. HBase 存储数据通过MapFiles (索引SequenceFiles), 是一个很好的选择如果你需要MapReduce流分析同时也需要随机访问查看. 如果延迟是一个问题, 然而这里有许多其他的选择 — 看Richard Jones’ 杰出的survey of key-value stores.