文档目录
什么是小文件
小文件的定义和hadoop中定义的block大小有关,这里把所有远远小于hadoop block大小的文件称为小文件。hadoop block块大小通常设置为128MB、256MB,趋向于越来越大。根据不同的需求,对小文件具体的判定规则也会不一样,这里假设为hadoop block 大小的75%,即大小小于hadoop block的大小的75%的文件都是小文件。
小文件产生的原因
- 数据采集过程中产生的小文件:
- 公司对实时数据的渴望,获取数据的时间间隔的缩短,导致采集进入hadoop的数据文件偏小
- 延迟的数据
- 源系统生成很多个小文件,这些文件无需修改即可直接复制到Hadoop中。
- MapReduce job的配置使用超过必要数量的reducer,每个reducer输出自己的文件。同样,如果数据倾斜导致大部分数据转到一个reducer,那么剩余的reducer将处理非常少的数据并产生小的输出文件。
小文件引起的问题
hadoop 需要处理小文件问题的主要原因有:
- NameNode的内存管理
NameNode的内存问题Hadoop中的每个目录,文件和块都表示为NameNode内存中的对象。根据经验,每个对象需要150个字节的内存。如果你有2000万个文件,每个文件需要1个块,你的NameNode需要6GB的内存。这显然是可行的,但随着系统的扩展,最终会达到NameNode可以处理的文件(块)数量的实际限制。假设所有文件都在同一个文件夹中,十亿个文件需要300GB的内存。让我们考虑300GB NameNode内存要求的影响。
- 当NameNode重新启动时,它必须从本地磁盘上的缓存中读取每个文件的元数据。这意味着从磁盘读取300GB的数据 - 可能会导致启动时间延迟。
- 在正常操作中,NameNode必须不断跟踪并检查群集中每个数据块的存储位置。这是通过监听数据节点来报告其所有数据块来完成的。数据节点必须报告的块越多,它将消耗的网络带宽就越多。即使节点之间存在高速互连,这种规模的简单块报告也可能会造成破坏性。
优化很明显。如果可以减少群集上的小文件数,则可以减少NameNode内存占用,启动时间和网络影响。
- MapReduce性能
拥有大量小文件会降低MapReduce处理的性能,无论是Hive,Pig,Cascading,Pentaho MapReduce还是Java MapReduce。第一个原因是大量的小文件意味着大量的随机磁盘IO。磁盘IO通常是MapReduce性能的最大限制因素之一。一次大的顺序读取总是优于通过几次随机读取读取相同数量的数据。如果可以将数据存储在更少、更大的块中,则可以减轻磁盘IO的性能影响。
性能下降的第二个原因有点复杂,需要了解MapReduce如何处理文件和调度资源。我将在此解释中使用MapReduce V1术语,因为它比使用Yarn更容易解释,但相同的概念适用于Yarn。当MapReduce作业启动时,它会为每个正在处理的数据块分配一个Mapper任务。存储在Hadoop中的每个文件至少有一个块。如果有10,000个文件,每个文件包含10 MB的数据,则MapReduce作业将安排10,000个Map任务。通常配置Hadoop,以便每个Mapper任务在其自己的JVM中运行。继续我们的例子,你将有10,000个JVM的开销!
Hadoop集群只有这么多资源。在MapReduce v1中,为避免节点过载,请指定节点可以处理的最大并发Mapper数。通常,并发Mapper的最大数量在5到20范围内。因此,要同时运行10,000个Mapper,必须拥有500到2000个节点。大多数Hadoop集群比这小得多,导致JobTracker在等待slot时候时对Mapper任务进行排队。如果有一个总共有100个slot的20个节点群集,那么队列将变得非常大并且过程将花费很长时间。不要忘记,这个工作可能不是竞争群集资源的唯一工作。
如果拥有800个128 MB的文件而不是10,000个10MB文件,那么您只需要800个Mapper任务。这将需要少一个数量级的JVM维护时间,并将导致更好的磁盘IO。即使处理128MB的单个Map任务将花费比处理10MB的Map任务更长的时间,但是当处理800个更大的文件时,所有处理时间的总和几乎总是要快几个数量级。
小文件解决方案
解决NameNode内存问题
Hadoop中每个块的元数据必须存储在NameNode的内存中。这导致实际限制Hadoop中可以存储的对象数量,并且还会影响启动时间和网络带宽。有两种解决方案,减少Hadoop集群中的对象数量或以某种方式使NameNode更多地使用内存 - 但不会导致过多的启动时间。解决此内存问题的最常见方法Hadoop Archive (HAR) Files 和 Federated NameNodes。
Hadoop Archive (HAR) Files
Hadoop Archive Files 通过将许多小文件打包到更大的HAR文件来缓解NameNode的内存问题,类似于Linux上的TAR文件。这导致NameNode保留单个HAR文件的信息,而不是数十个或者数百个小文件。可以使用har://前缀而不是hdfs://来访问HAR文件中的文件。HAR文件是HDFS中存在的文件创建的。因此,一个HAR文件可以合并摄取的数据和通过普通MapReduce处理创建的数据。。HAR文件可以独立于用于创建小文件的技术使用。除了HDFS之外,没有其他常见的依赖关系。
虽然HAR文件会降低由于小文件产生的内存占用,但是访问和处理HAR文件内容的效率也有可能降低。HAR文件依然随机存储在磁盘上,访问一个HAR内的文件要经过两次索引:第一次是在NameNode中找到HAR文件;第二次是在HAR文件中找到目标文件。读取一个HAR中的文件实际上可能要比读取没有经过处理的存储在hdfs中同样的文件要慢。MapReduce作业会加剧这个性能问题,因为它们仍然会在HAR中为每个文件启动一个map任务。
最后,需要衡量一下:HAR文件可以解决NameNode内存问题,但可能会降低处理性能。如果集群中的小文件主要是用于存档,并且很少访问,那么HAR文件是处理小文件问题的很好的方案。如果小文件是常规数据处理流程中的一部分,则可能需要重新考虑设计。
Federated NameNode
Fedeated NameNode 允许在集群中拥有多个NameNode,每个NameNode存储对象元数据的一个子集。Federated NameNode解决了把对象元数据都存储在一台机器上的问题,为存储元数据的内存使用提供了更好的扩展性。表面看起来,使用这种技术来解决小文件问题很有吸引力,但是稍加思考你就会意识到这个技术的局限性。
Federated NameNodes隔离对象的元数据-只有一个NameNode知道某个特定的对象的元数据。这就意味这如果你要获取一个文件,你得先确定文件对应的对象的元数据存放在那个NameNode。如果集群中包含多个租户、多个单独的应用,或两者的组合,那么Federated NameNode是很合适的方案。
由于Federated NameNode实际上并不会改变集群中对象或块的数量,因此它不能解决Mapreduce性能问题。相反,Federated NameNode会给Hadoop的安装和管理增加很多必要的复杂性。当Federated NameNode用于解决小文件问题的时候,更多是因此小文件问题的机制。
解决MapReduce性能问题
MapReduce性能问题是由随机磁盘IO和启动/管理太多map任务的组合引起的。解决方案似乎很明显 - 拥有更少,更大的文件或启动更少的map任务; 然而,这说起来容易做起来难。一些常见的解决方案包括:
- 改变摄取过程/间隔
- 批处理文件合并
- 序列文件
- HBase
- S3DistCp(如果使用Amazon EMR)
- 使用CombineFileInputFormat
- Hive配置设置
- 使用Hadoop的append功能
改变摄取过程/间隔
摆脱小文件的最简单方法就是尽量不生成它们。如果源系统生成数千个复制到Hadoop的小文件,请更改源系统以生成一些大文件,或者在摄取到HDFS时进行连接文件。如果每小时仅摄取10 MB数据,请确定是否每天只能摄取一次。将创建1x240MB文件而不是24x10MB文件。但是,可能无法控制创建文件的源系统或业务需求要求以间隔频率摄取数据,以便小文件不可避免。如果小文件确实是不可避免的,那么应该考虑其他解决方案。
批处理文件合并
当小文件不可避免的时候,文件合并是最常用的解决方案。使用这个方案,可以定期运行一个简单的合并MapReduce的作业来读取文件夹中的所有小文件,并将它们重写为更大的文件。如果文件夹中有1000个文件,MapReduce合并作业里指定的文件数为5,则1000个输入文件将合并为5个输出文件。在一些简单的HDFS的文件\文件夹操作后,将内存占用减少了200:1,并且可能提高对相同数据未来MapReduce处理的性能。
在Hive或Java MapReduce中实现它同样容易。这些MapReduce作业在执行时需要集群资源,通常安排在非工作时间内。但是,它们应该足够频繁得运行,因此小文件的性能影响不会变得太极端。通常会在这些作业内置其他逻辑,以便仅合并文件夹内的文件,这些文件会对性能有显著的影响。合并仅包含三个文件的文件夹不会像在包含500个小文件的文件夹中进行合并那样带来性能的优势。
检查文件夹以确定合并哪些文件夹可以通过多种方式完成。有一个专门为此任务设计的预编写应用程序名为File Crush,这是一个由Edward Capriolo编写的开源项目。File Crush不受专业支持,因此不保证它将继续与未来版本的Hadoop一起使用(可以参考其实现思路)。
批处理文件合并不会保留原始文件名。如果文件的原始文件名对于处理或者了解数据来源非常重要,则批处理合并将不会起作用。但是,大多数HDFS设计在文件夹级别而不是在每个文件中嵌入命名语义。采用这种方法会将文件名依懒性作为一个问题删除。
序列文件(Sequence File)
当需要维护原始文件名的时候,一种非常常见的方法就是使用序列文件。在此解决方案中,文件名作为密钥存储在序列文件中,文件内容作为值存储。下表给出了如何将小文件存储在序列文件中的示例:
key | value | key | value | key | value |
---|---|---|---|---|---|
file1.txt | file1 contents | file2.txt | file2 contents | file3.txt | file3 contents |
如果有1000个文件,则序列文件将包含1000个密钥,每个文件一个。序列文件支持块压缩,并且是可拆分,这意味着MapReduce作业只能为每个128M的文件启动一个Map任务,而不是每个小文件启动一个映射任务。当需要维护输入文件名,并且同时大量的小文件时候,这个方案非常有效。
但是,如果一次只需要摄取少量的文件,则序列文件不能正常工作,因为Hadoop文件是不可变更且无法追加的。三个10MB产生的30MB的序列文件,根据我们的定义,还是属于小文件。另外一个挑战就是检查序列文件的文件名列表需要处理整个文件。
此外,Hive与此结构中的序列文件不兼容。Hive将值中所有的数据视为单行。使用Hive查询此数据并不容易,因为文件的整个内容将是Hive中的单行。最后,创建的Hive表将无法访问序列文件中的密钥(文件名),只能访问值(文件内容)。可以编写自定义的Hive Serde 来解决这些挑战,但这是一个超过了Hadoop本身功能的高级话题了。
HBase
如果要生产大量的小文件,将数据作为文件存储在HDFS中可能不是最佳解决方案。此时,可以考虑使用HBase进行存储。如果使用HBase可以将摄取过程从生成许多小型的文件更改为将单个记录写入HBase表。如果数据访问模式基于明确定义的随机访问查找,则HBase可能是一个很好的选择。它在架构上针对高速的数据插入、大容量、单个记录查找和基于流的分析进行了调整。但是,如果访问模式倾向于完整文件\表扫描,那么HBase可能不是一个很好的选择。
可以创建映射到HBase数据的Hive表,但是,这种设计中的查询性能会有所不同。当选择单行或者一系列行时候,HBase上的Hive的表现会让你眼前一亮,当你的查询倾向全表扫描时候,则HBase的效率非常低。大多数就分析,尤其是那些使用分组查询的查询,都需要进行全表扫描。
HBase提供了奖数据流式传输到Hadoop并使其可实时处理的能力。但是,平衡HBase与其他集群进程的需求可能具有挑战性,并且需要高级系统管理。此外,HBase的访问性能很大程度上取决于数据的访问模式,在选择HBase处理小文件问题时候,应仔细考虑这些。
S3DistCp
此解决方案仅适用于Amazon EMR的用户。Amazon EMR集群设计为短期存储,并在Amazon S3中保留其数据。即使使用Amazon S3,处理大量小文件仍会导致启动比必要更多的Map任务 - 降低性能。输入S3DistCp ...
S3DistCp是Amazon提供的一种实用程序,用于将数据从S3分发复制到临时HDFS甚至其他S3存储桶。该实用程序提供了通过使用groupBy和targetSize选项将文件连接在一起的功能。当S3中存储了数千个要使用Amazon EMR处理的小文件时,这非常有用。S3DistCp通过连接许多小文件并使它们出现在更快,短暂的HDFS存储中,一举两得。据报道,使用这种机制可以提高15倍的性能。
目的,S3DistCp执行与之前提到的批处理文件合并方法相同的任务。如果使用Amazon EMR,请注意您有一个预先构建的工具来完成此任务。
使用CombineFileInputFormat
CombineFileInputFormat是Hadoop提供的抽象类,它在MapReduce读取时合并小文件。合并的文件并不会保存到磁盘。相反,该过程读取多个文件并“动态”合并它们以供单个Mapper任务使用。可以获得不为每个文件启动一个Map任务的好处,并且不需要将多个文件合并到一个持久的文件作为准备步骤的一部分。这解决了MapReduce作业启动太多Map任务的问题,但是由于作业仍然需要读取多个小文件,随机磁盘的IO仍然是一个问题。此外,大多数CombineFileInputFormat实现不考虑数据局部性,通常会从网络上的各种数据节点提取数据。
为了实现这一点,必须在Java中为不同的文件类型扩展CombineFileInputFormat。这需要大量的开发专业知识来开发对应的自定义输入格式。但是,一旦编写完成,可以配置一个最大分割大小,它将合并文件,直到满足这个大小。
请注意:由于合并的文件并不会在磁盘中保留,因此CombineFileInputFormat不会环境NameNode的内存问题。
Hive Configuration
如果有注意到Hive通过“create table as”和“insert overwrite”语句在Hadoop集群中创建小文件,则可以调整一些Hive的特定的配置来减轻影响。使用时,这些设置会告诉Hive将创建的任何小文件合并到大文件中。但是,这样是有惩罚的,Hive将在查询后额外启动一个MapReduce作业来完成合并任务。此外,合并是在Hive向用户指示查询已经完成处理而不是异步发生之前完成的。
应该注意,这些设置仅适用于由Hive创建的文件。例如,如果使用其他工具(如Sqoop)在Hive外部创建文件,则使用hdfs fs -mv命令将其复制到Hive表中,Hive将不会合并文件。因此,当摄入Hadoop的文件很小时,此解决方案不起作用。此解决方案仅建议在以Hive为中心的体系结构中,其中insert overwrite和create table as语句中的小性能损失是可接受的。需要使用的配置是:
Property | Description | default value |
---|---|---|
hive.merge.mapfiles | Merge small files that are produced from the map-only jobs | true |
hive.merge.maprefiles | Merge small files that are produced from the map-reduce jobs | false |
hive.merge.size.per.task | When merging small files the target size for the merge files at the end of the job | 256 000000(in bytes) |
hive.merge.samllfiles.avgsize | when the average size of the ouput file is less than this number,hive will execute an additional MapReduce Job to merge the files based on hive.merge.mapfiles and hive.merge.maprefiles | 16 000000(in bytes) |
使用Hadoop的append功能
在Hadoop中append功能的故事相当崎岖。作为Hadoop 0.19的一部分,附加在2008年7月添加。但是,在实施之后(早在2008年10月),发现了许多问题,并在0.19.1中禁用了追加。但是,为了支持HBase而没有数据丢失的风险,附加功能在0.20.2中被添加回Hadoop。所以,最后,在0.20.2之后,技术上可以在Hadoop中执行追加。
append可能是可用的,但Hadoop生态系统中的主要工具都不支持它:Flume,Sqoop,Pig,Hive,Spark和Java MapReduce。MapReduce强制执行一条规则,即MapReduce作业的输出位置在执行之前不得存在。由于这个规则,MapReduce显然不可能通过其输出追加到预先存在的文件。由于Sqoop,Pig和Hive都使用了MapReduce,因此这些工具也不可能支持追加。Flume不支持追加主要是因为它假设经过一段时间(无论是秒,字节,事件数或不活动秒),Flume将关闭文件而不再打开它。Flume社区认为这足够,不需要追加支持。
如果真的必须在Hadoop中使用append,必须编写的系统来执行摄取并附加到现有文件。此外,如果您的任何群集内处理需要追加到现有文件,将无法使用Spark或MapReduce。因此,使用HDFS append功能非常复杂,只能由技术最精湛的组织使用。如果没有重要的工程团队和支持承诺,建议不要使用此选项。
如何选择小文件解决方案
选择使用小文件的最佳解决方案取决于各种问题。可能有必要根据访问模式和数据要求使用这些解决方案的组合。应该考虑的问题包括:
数据流中的哪一点是生成的小文件?是在摄取时还是通过群集内处理创建小文件?
生成小文件的工具是什么?更改工具配置可以减少小文件的数量吗?
组织内存在哪些技术技能?是否有能力维护输入格式或编写自己的摄取引擎?
生成小文件的频率是多少?为了创建大文件,可以多久合并一次小文件?
这些小文件需要什么样的数据访问?是否需要通过Hive访问文件?
可以在集群内部运行流程以减轻小文件的管理周期类型?
MapReduce流程可接受的延迟级别是多少?注意:这里的MapReduce流程不单单指的是编写的MapReduce程序,也可以是在其他计算引擎中使用到MapReduce的Api