大数据篇--小文件

一、小文件定义

  小文件是指文件大小明显小于 HDFS 上块(block)大小(Hadoop1.x中默认64MB,在Hadoop2.x中默认为128MB)的文件。
 

二、为什么会有小文件

  Hadoop中的目录、文件、block都会以元数据(MetaData)的方式存储下来的,他们每一个的元数据大小还是不一样的,如果感兴趣的话可以翻一下Hadoop的源码(有详细的讲解,每一个目录、文件、block大概占用的内存是多少),一般情况下都是150字节左右,所以说如果你有几千万上亿个文件你想想NameNode需要多大的内存空间?(如果有1千万个文件,每一个文件对应一个block,那么就将要消耗namenode大概2G左右的内存来保存这些block的信息。如果规模再大一些,那么将会超出现阶段计算机硬件所能满足的极限。)

  处理小文件并非 Hadoop 的设计目标,HDFS 的设计目标是流式访问大数据集(TB级别)。因而,在 HDFS 中存储大量小文件是很低效的。

  所以说小文件一定要想办法规避,这东西不可避免会产生但是一定要规避,如果小文件非常多当你NameNode启动的时候它需要从本地磁盘里把元数据加载到内存中去并合并,它就会耗费很长的时间,这就是前面说的NameNode HA起不来的一个问题(主挂掉之后备起了好几个小时都起不来)。不要以为小文件这个东西不起眼,这个东西是非常非常影响Hadoop集群性能的,这里所说的性能不单单指的是HDFS,如果你的Spark、Flink是跑在Yarn上面,如果你block的数量也很多的话,照样这些组件的性能会急剧下降。

1.数据迁移过程中:

  我们知道如果想要进行大数据处理的话最通常的做法是把数据拷贝到HDFS上,假设你的数据源上面已经有非常非常多的小文件了,但是你根本没有关注(没有做很好的设置)就直接拷贝到了HDFS上,这是一个数据搬迁的过程,不管是你手工搬过去的还是通过Flume把数据采集到HDFS上,都非常常见有这种情况的发生。

2.处理源头文件:

  当你在做批处理或者离线计算的时候,每小时或者每天甚至每五分钟做生成的时候肯定就会有小文件的一个产生,特别你是在用Spark或者MapReduce处理的时候,如果你源头的那个文件没有做一个很好的规避,没有做一个很好的设置大小处理,最后会出来的小文件是非常多的,而且占用的block也会非常多。

3.处理过程中产生:

  如果你在Sprk或者MapReduce作业的时候,reduce没有做很好设置的话,MapReduce中的reduce是决定了文件输出个数的,对于Spark来说partition的数量也就决定了输出文件的多少,这两者你如果在编程的时候没有做到一个很好的设置,比如在shuffle的时候肯定要做一个合理的设置,如果你不设置就很定会有很多小文件的生成。
 

三、小文件给Hadoop集群带来的瓶颈问题

下图是非常经典的一个MapReduce执行过程(面试常考点):
在这里插入图片描述
  如果集群里面有很多小文件其实必然会导致MapReduce整体性能的一个下降,这里只是以MapReduce为例,如果是Hive、Spark、Flink都是同样的道理,它都是一样的过程(只不过是有些名词在一些框架里的叫法不同而已)。

  第一点是当Input读的时候如果你有大量的小文件会产生大量的磁盘IO(其实磁盘的IO是执行框架不管是MapReduce还是Spark等等非常非常受影响的,算是最大的瓶颈之一)。你可以想一个问题,如果有100个小文件相当于你要随机读会有100g个IO,那如果把100个文件合并成一个那么读这个一个大的肯定要比读100个耗费的时间要少很多,所以处理大量小文件速度远远小于处理同等大小的大文件的速度。

  第二点就是在MapReduce过程中一个文件就要一个Map Task,一百个文件就要一百个Map Task进行处理,那么Map和Reduce Task的开启和销毁也会消费大量时间的(MapReduce里面task默认都是进程级别的,进程的话肯定要开启和销毁,这个资源开销是非常大的)。

  第三点是你集群中的资源是有限的,那么你Map的数量非常非常多,但是你的节点数量有限,会使很多作业一直处在等待申请资源状态。所以说能合并的合并以后,通过减少跑完的轮次,你整体的性能肯定是可以提升的。
 

四、如何解决小文件

1.Hadoop中:

  hadoop中有一些特性可以用来减轻这种问题:可以在一个JVM中允许task reuse,以支持在一个JVM中运行多个map task,以此来减少一些JVM的启动消耗(Hadoop 1.x通过设置mapred.job.reuse.jvm.num.tasks属性,默认为1,-1为无限制)。

可参考:
https://www.iteye.com/blog/jerrylead-1195335
https://blog.csdn.net/xu2414506319/article/details/109136221
https://blog.csdn.net/weixin_43228814/article/details/88883310

(1)自带方案Hadoop Archive:

  hadoop archive 命令运行MapReduce job来并行处理输入文件,将小文件的内容合并形成少量大文件,然后再利用 index 文件,指出小文件在大文件中所属的坐标,以此来减少小文件的数量。Hadoop Archives生成归档文件格式为HAR。
  Hadoop Archive是一个高效地将小文件放入HDFS块中的文件存档工具。它能将多个小文件打包成一个HAR文件,这样在减少NameNode内存使用的同时,仍然允许对小文件进行透明的访问,比如作为MapReduce的输入。

存在的问题:

  • 存档文件的源文件及目录都不会自动删除,需要手动删除
  • 存档过程实际是一个MapReduce过程,所以需要hadoop的MapReduce支持
  • 存档文件本身不支持压缩
  • 存档文件一旦创建便不可修改,要想从中删除或增加文件,必须重新建立存档文件
  • 创建存档文件会创建原始文件的副本,所以至少需要有与存档文件容量相同的磁盘空间
  • 使用 HAR 作为MR的输入,MR可以访问其中所有的文件。但是由于InputFormat不会意识到这是个归档文件,也就不会有意识的将多个文件划分到单独的Input-Split中,所以依然是按照多个小文件来进行处理,效率依然不高。并且尽管HAR文件可以被用来作为MapReduce job的input,但是并没有特殊的方法来使maps将HAR文件中打包的文件当作一个HDFS文件处理。可以考虑通过创建一种input format,利用HAR文件的优势来提高MapReduce的效率,但是目前还没有人作这种input format。需要注意的是:MultiFileInputSplit,即使在HADOOP-4565的改进(choose files in a split that are node local),但始终还是需要seek per small file。如果想深研究MultiFileInputSplit的话可参考:通过MultipleInputs处理多输入文件
  • HAR结构:二级索引
    在这里插入图片描述
    可参考:https://www.cnblogs.com/skyl/p/4758364.html
(2)Sequence File:

  使用Sequencefile作为表存储格式,不要用textfile,在一定程度上可以减少小文件。Sequence File由一系列二进制的键值对组成,其中key为小文件的名字,value的File Content。它提供key-value的存储,但与传统key-value存储(比如hash表,btree)不同的是,它是appendonly的,于是你不能对已存在的key进行写操作。创建Sequence File的过程可以使用MapReduce Job完成。Sequence Files也是可以被切分的,所以能够作为MapReduce的输入分片,并且分别被独立的处理。和HAR不同的是,这种方式还支持压缩。block压缩在许多情况下都是最好的选择。
  SequenceFile的写入由SequenceFile.Writer来实现, 根据压缩类型SequenceFile.Writer又派生出两个子类SequenceFile.BlockCompressWriter和SequenceFile.RecordCompressWriter, 压缩方式由SequenceFile类的内部枚举类CompressionType来表示,定义了三种方式。

坏处:是需要一个合并文件的过程,且合并后的文件将不方便查看。

可参考:https://blog.csdn.net/bitcarmanlee/article/details/78111289

TextFile:Hive数据表的默认格式,存储方式:行存储。 可以使用Gzip压缩算法,但压缩后的文件不支持split 在反序列化过程中,必须逐个字符判断是不是分隔符和行结束符,因此反序列化开销会比SequenceFile高几十倍。

Sequence Files:Hadoop中有些原生压缩文件的缺点之一就是不支持分割,支持分割的文件可以并行的有多个mapper程序处理大数据文件,大多数文件不支持可分割是因为这些文件只能从头开始读。Sequence File是可分割的文件格式,支持Hadoop的block级压缩。 Hadoop API提供的一种二进制文件,以key-value的形式序列化到文件中。存储方式:行存储。 sequencefile支持三种压缩选择:NONE,RECORD,BLOCK。Record压缩率低,RECORD是默认选项,通常BLOCK会带来较RECORD更好的压缩性能。 优势是文件和hadoop api中的MapFile是相互兼容的。

Parquet列式存储。 它具有架构支持。 它与Hive和Spark配合使用非常好,可以将列数据存储在使用SQL查询的深度存储中。 因为它将数据存储在列中,所以查询引擎将只读取具有选定列的文件,而不读取与Avro相反的整个数据集的文件。

ORCFile:ORC的全称是(Optimized Row Columnar),ORC文件格式是一种Hadoop生态圈中的列式存储格式,它的产生早在2013年初,最初产生自Apache Hive,用于降低Hadoop数据存储空间和加速Hive查询速度。和Parquet类似,它并不是一个单纯的列式存储格式,仍然是首先根据行组分割整个表,在每一个行组内进行按列存储。ORC文件是自描述的,它的元数据使用Protocol Buffers序列化,并且文件中的数据尽可能的压缩以降低存储空间的消耗,目前也被Spark SQL、Presto等查询引擎支持,但是Impala对于ORC目前没有支持,仍然使用Parquet作为主要的列式存储格式。

Avro:非常适合存储行数据,非常高效。与Kafka的完美集成。 支持文件分割。 用于行级操作或在Kafka中。 写数据很棒,读起来慢。

(3)CombineFileInputFormat类:

  MR-Job默认的输入格式FileInputFormat为每一个小文件生成一个切片。CombineFileInputFormat是Hadoop自带的多文件合并处理方案。指定输入目录,将其下的大量小文件进行合并分片,达到减少map任务数量的目的。

可参考:https://www.cnblogs.com/skyl/p/4754999.html

2.Hive中:

通过参数进行调节,设置map/reduce端的相关参数,如下:

设置map输入合并小文件的相关参数:

//每个Map最大输入大小(这个值决定了合并后文件的数量)
set mapred.max.split.size=256000000;
//一个节点上split的至少的大小(这个值决定了多个DataNode上的文件是否需要合并)
set mapred.min.split.size.per.node=100000000;
//一个交换机下split的至少的大小(这个值决定了多个交换机上的文件是否需要合并)
set mapred.min.split.size.per.rack=100000000;
//执行Map前进行小文件合并
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;

设置map输出和reduce输出进行合并的相关参数:

//设置map端输出进行合并,默认为true
set hive.merge.mapfiles = true
//设置reduce端输出进行合并,默认为false
set hive.merge.mapredfiles = true
//设置合并文件的大小
set hive.merge.size.per.task = 25610001000
//当输出文件的平均大小小于该值时,启动一个独立的MapReduce任务进行文件merge。
set hive.merge.smallfiles.avgsize=16000000
3.Sparkstreaming中:

  使用Sparkstreaming时,如果实时计算结果要写入到HDFS,那么不可避免的会遇到一个问题,那就是在默认情况下会产生非常多的小文件,这是由sparkstreaming的微批处理模式和DStream(RDD)的分布式(partition)特性导致的,sparkstreaming为每个partition启动一个独立的线程来处理数据,一旦文件输出到HDFS,那么这个文件流就关闭了,再来一个batch的parttition任务,就再使用一个新的文件流,那么假设,一个batch为10s,每个输出的DStream有32个partition,那么一个小时产生的文件数将会达到(3600/10)*32=11520个之多。众多小文件带来的结果是有大量的文件元信息,比如文件的location、文件大小、block number等需要NameNode来维护,NameNode会因此压力山大。不管是什么格式的文件,parquet、text,、JSON或者 Avro,都会遇到这种小文件问题,这里讨论几种处理Sparkstreaming小文件的典型方法。

(1)SparkStreaming外部来处理:

  我们既然把数据输出到hdfs,那么说明肯定是要用hive或者sparksql这样的“sql on hadoop”系统类进一步进行数据分析,而这些表一般都是按照半小时或者一小时、一天,这样来分区的(注意不要和sparkStreaming的分区混淆,这里的分区,是用来做分区裁剪优化的),那么我们可以考虑在SparkStreaming外再启动定时的批处理任务来合并SparkStreaming产生的小文件。这种方法不是很直接,但是却比较有用,“性价比”较高,唯一要注意的是,批处理的合并任务在时间切割上要把握好,搞不好就可能会去合并一个还在写入的SparkStreaming小文件。

(2)调用foreach去append:

  SparkStreaming提供的foreach这个output类api,可以让我们自定义输出计算结果的方法。那么我们其实也可以利用这个特性,那就是每个batch在要写文件时,并不是去生成一个新的文件流,而是把之前的文件打开。考虑这种方法的可行性,首先,HDFS上的文件不支持修改,但是很多都支持追加,那么每个batch的每个partition就对应一个输出文件,每次都去追加这个partition对应的输出文件,这样也可以实现减少文件数量的目的。这种方法要注意的就是不能无限制的追加,当判断一个文件已经达到某一个阈值时,就要产生一个新的文件进行追加了。

4.Spark SQL中

  生产上,我们往往将Spark SQL作为Hive的替代方案,来获得SQL on Hadoop更出色的性能。小文件问题也会给Spark SQL等查询引擎造成查询性能的损耗,大量的数据分片信息以及对应产生的Task元信息也会给Spark Driver的内存造成压力,带来单点问题。此外,入库操作最后的commit job操作,在Spark Driver端单点做,很容易出现单点的性能问题。
  动态分区插入数据,没有Shuffle的情况下,输入端有多少个逻辑分片,对应的HadoopRDD就会产生多少个HadoopPartition,每个Partition对应于Spark作业的Task(个数为M),分区数为N。最好的情况就是(M=N) && (M中的数据也是根据N来预先打散的),那就刚好写N个文件;最差的情况下,每个Task中都有各个分区的记录,那文件数最终文件数将达到M * N个。这种情况下是极易产生小文件的。

解决可参考:https://blog.csdn.net/weixin_43228814/article/details/88883310

  • 0
    点赞
  • 5
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页
评论

打赏作者

小强签名设计

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值