CombineInputFormat在hive和MapReduce的应用和原理

CombineFileInputFormat作用:将多个小文件打包成一个InputSplit提供给一个Map处理,避免因为大量小文件问题,启动大量任务

CombineFileInputFormat是一种新的inputformat,用于将多个文件合并成一个单独的split,另外,它会考虑数据的存储位置。旧版本的MultiFileInputFormat是按文件单位切分,可能造成split不均匀,如果有一个大文件则会单独由一个map处理,严重偏慢

CombineFileInputFormat是个抽象类,需要手工实现

CombineInputFormat的应用

Hive 使用设置

set mapred.max.split.size=256000000; //合并的每个map大小

Set mapred.min.split.size.per.node=256000000 //控制一个节点上split的至少的大小,按mapred.max.split.size大小切分文件后,剩余大小如果超过mapred.min.split.size.per.node则作为一个分片,否则保留等待rack层处理

Set Mapred.min.split.size.per.rack=256000000 // 控制一个交换机下split至少的大小,合并碎片文件,按mapred.max.split.size分割,最后若剩余大小超过 Mapred.min.split.size.per.rack则作为单独的一分片

最后合并不同rack下的碎片,按mapred.max.split.size分割,剩下的碎片无论大小作为一个split

set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat

MapReduce设置

需要自定义MyMultiFileInputFormat,详见博客hadoop CombineFileInputFormat的使用

CombineInputFormat的原理

HDFS本身被设计来存储大文件,但是有时难免会有小文件出现,有时很可能时大量的小文件。通过MapReduce处理大量小文件时会遇到些问题。

MapReduce程序会将输入的文件进行分片(Split),每个分片对应一个map任务,而默认一个文件至少有一个分片,一个分片也只属于一个文件。这样大量的小文件会导致大量的map任务,导致资源过度消耗,且效率低下。Hadoop自身包含了CombineFileInputFormat,其功能是将多个小文件合并如一个分片,由一个map任务处理,这样就减少了不必要的map数量。

在了解CombineFileInputFormat之前,我们应了解其父类FileInputFormat的基本处理逻辑。注意这里的FileInputFormat的路径是org.apache.hadoop.mapreduce.lib.input.FileInputFormat,是新的MapReduce API。 mapred包下的FileInputFormat对应老的API,不再推荐使用。

1.FileInputFormat的基本处理逻辑

FileInputFormat是基于文件的InputFormat的抽象基类,基于文件的衍生类有很多,如文本文件TextInputFormat,序列文件SequenceFileInputFormat等。

FileInputFormat提供了分片的基本实现getSplits(JobContext),其子类可以重写isSplitable(JobContext, Path)方法,来使得输入的一个或多个文件是否不做分片,完整由被一个Map任务进行处理。

FileInputFormat有如下特点:

  1. 将以下划线”_”或点”.”开头的文件作为隐藏文件,不做为输入文件
  2. 所有文件isSplitable(JobContext, Path)都是true,但是针对如果输入文件时压缩的、流式的,那么子类中应重新该函数,判断是否真的可以分片。
  3. 由于每个文件块大小可能不一样,所以每个文件分别计算分片大小,计算规则如下:
    1. 取值A:
      • FormatMinSplitSize,本Format设置的最小Split大小,通过GetFormatMinSplitSize()获取,此类中定义为1个字节。
      • MinSplitSize,配置文件(配置键mapreduce.input.fileinputformat.split.minsize)或者直接设置,通过GetMinSplitSize()获取),未设置则为0。
      • 取两者较大值。
    2. 取值B:
      • MaxSplitSize,通过配置文件设置或者SetMaxSplitSize()设置,通过GetMaxSplitSize()获取,无设置则取LONG.MAXVALUE。
      • 文件块大小BLOCKSIZE
      • 取两者较小值
    3. 再取A、B的较大值。
    4. 例:常用的TextInputFormat类中,没有对分片算法进行重写,那么我们可以认为,使用TextInputFormat时,在未做其他设置的情况下,默认分片大小等于BLOCKSIZE,如果设置了mapreduce.input.fileinputformat.split.minsize,则取其与BLOCK的较大值。
  4. 分片大小确定后,就将文件进行依次划分。

2.文本类型TextInputFormat的使用

如上我们知道TextInputFormat是FileInputFormat的子类,其自定义内容很少,通过它可以大致知道扩展FileInputFormat大致需要做些什么。整个类如下:

public class TextInputFormat extends FileInputFormat<LongWritable, Text> {
 
  @Override
  public RecordReader<LongWritable, Text>
    createRecordReader(InputSplit split,
                       TaskAttemptContext context) {
    String delimiter = context.getConfiguration().get(
        "textinputformat.record.delimiter" );
    byte[] recordDelimiterBytes = null;
    if ( null != delimiter)
      recordDelimiterBytes = delimiter.getBytes(Charsets. UTF_8);
    return new LineRecordReader(recordDelimiterBytes);
  }
 
  @Override
  protected boolean isSplitable(JobContext context, Path file) {
    final CompressionCodec codec =
      new CompressionCodecFactory(context.getConfiguration()).getCodec(file);
    if ( null == codec) {
      return true;
    }
    return codec instanceof SplittableCompressionCodec;
  }
}

分片的方式采用的是父类FileInputFormat的逻辑,上文中已经说明。重写了isSplitable(),根据文本文件的压缩属性来判断是否可以进行分片。
而createRecorderReader()是定义文本文件的读取方式,实际文件读取是通过它返回的RecordReader<LongWritable, Text>类实现的。

这样,在整个输入文件读取过程,大致会涉及如下几个步骤:

  1. 指定输入文件路径,如FileInputFormat.addInputPaths(job, args[0])
  2. 指定文件的处理类型,如job.setInputFormatClass(MyInputFormat. class)
    在这个InputFormatClass内部,考虑:
    是否可以进行分片 isSplitable(JobContext context, Path file)
    如何分片 List getSplits(JobContext job)
  3. 如何读取分片中的记录 RecordReader<LongWritable, Text> createRecordReader(InputSplit split,TaskAttemptContext context)
  4. 以上确定后,用户开发的Map任务中就可以直接处理每一条记录KeyValue。

这些步骤下,我们基本就可以理解TextInputFormat如何被使用了。其他类型的InputFormat子类,其流程步骤也基本与上相同,可以重写相关的类方法来实现不同处理方式。

从TextInputFormat中的分片逻辑(FileInputFormat的getSplits)中可以确定,分片都是针对单个文件而言的,如果文件本身较小,没有达到一个分片大小,那么每个小文件都是一个分片。而一个分片就对应一个Map任务。如果有大量的小文件作为Map的输入,那么其会导致生成大量Map任务,造成处理的缓慢、资源的浪费,如何减少map任务的数量提高处理效率呢?CombineInputFormat就是为解决这样的问题。

3.CombineInputFormat原理与用法

CombineInputFormat的功能,是将一个目录(可能包括多个小文件,不包括子目录)作为一个map的输入,而不是通常使用一个文件作为输入。

CombineInputFormat本身是个抽象类,要使用它,涉及:

1)CombineFileSplit

我们的目标是使得一个split不是属于一个文件,而是可能包含多个文件,所以这里不再使用常用的FileSplit,而是CombineFileSplit,包括了各个文件的路径、长度、读的起始位置等信息。CombineFileSplit是CombineInputFormat中getSplits()的对象类型。

2)CombineInputFormat 核心处理类

2.1)其基本思想:

分片从指定路径下的多个文件构建,不同文件可以放入不同的pool,一个分片只能包含一个pool中的文件,可以包括多个文件的Block。pool其实是针对文件进行了逻辑划分,不同的pool中的文件分别进行分片。分片的逻辑如下文所示。

2.2)分片的逻辑:

  1. 如果指定了maxSplitSize(“mapreduce.input.fileinputformat.split.maxsize”),那么在同一个节点上的Blocks合并,一个超过maxSplitSize就生成新分片。如果没有指定,则只汇总本节点BLock,暂不分片。
  2. 如果指定了minSizeNode(“mapreduce.input.fileinputformat.split.minsize.per.node”),那么会把1.中处理剩余的Block,进行合并,如果超过minSizeNode,那么全部作为一个分片。否则这些Block与同一机架Rack上的块进行合并。
  3. 每个节点上如上同样的方式处理,然后针对整个Rack的所有Block,按照1.方式处理。剩余部分,如果指定了minSizeRack(“mapreduce.input.fileinputformat.split.minsize.per.rack”),并且超过minSizeRack,则全部作为一个分片,否则这些Block保留,等待与所有机架上的剩余Block进行汇总处理。
  4. 每个机架上都按照1,2,3方式处理,汇总所有处理剩下的部分,再按照1的逻辑处理。再剩余的,作为一个分片。
    以上逻辑我们可以知道:

如果只设置maxSplitSize(如job.getConfiguration().set( “mapreduce.input.fileinputformat.split.maxsize” , “33554432″)),那么基本每个分片大小都需凑满maxSplitSize。

如果maxSplitSize,minSizeNode,minSizeRack三个都没有设置,那是所有输入整合成一个分片!

3)CombineFileRecordReader

针对一个CombineFileSplit分片的通用RecordReader。CombineFileSplit中包含多个文件的块信息,CombineFileRecordReader是文件层面的处理,例如何时切换到分片中的下一个文件,而单个文件的处理,则需要自定义RecordReader的子类,读取文件的记录。

hadoop自带的示例应用org.apache.hadoop.examples.MultiFileWordCount,用到了CombineInputFormat,其处理流程:

MultiFileWordCount

要使用CombineInputFormat进行应用开发,可以参考org.apache.hadoop.examples.MultiFileWordCount中使用方式,需要自行实现CombineFileInputFormat的子类与实际读取逐条记录的RecordReader子类。

而MultiFileWordCount的使用如下:

shell> hadoop fs -ls -h /tmp/carl/2013-07-12/
Found 4 items
-rw-r–r– 3 hdfs hadoop 246.6 M 2013-12-03 13:07 /tmp/carl/2013-07-12/000059_0
-rw-r–r– 3 hdfs hadoop 244.9 M 2013-12-03 13:11 /tmp/carl/2013-07-12/000124_0
-rw-r–r– 3 hdfs hadoop 244.9 M 2013-12-03 13:15 /tmp/carl/2013-07-12/000126_0
-rw-r–r– 3 hdfs hadoop 85.2 M 2013-12-03 13:17 /tmp/carl/2013-07-12/000218_0

shell> hadoop jar hadoop-mapreduce-examples-2.0.0-cdh4.4.0.jar multifilewc /tmp/carl/2013-07-12/ /tmp/carl/c8/

13/12/24 19:31:23 INFO input.FileInputFormat: Total input paths to process : 4
13/12/24 19:31:23 INFO mapreduce.JobSubmitter: number of splits:1

Job Counters
Killed reduce tasks=1
Launched map tasks=1
Launched reduce tasks=17
Other local map tasks=1
Total time spent by all maps in occupied slots (ms)=37923
Total time spent by all reduces in occupied slots (ms)=1392681

因为MultiFileWordCount没有设置maxSplitSize,所以这里只有一个分片。

4.CombineInputFormat应用

4.1.使用场景
CombineInputFormat处理少量,较大的文件没有优势,相反,如果没有合理的设置maxSplitSize,minSizeNode,minSizeRack,则可能会导致一个map任务需要大量访问非本地的Block造成网络开销,反而比正常的非合并方式更慢。

而针对大量远小于块大小的小文件处理,CombineInputFormat的使用还是很有优势。

4.2.测试
我们以hadoop的示例程序WordCount和MulitFileWordCount来处理1000个小文件为例进行对比。

生成小文件:
shell> cat file_gen.sh
#!/bin/sh
for i in ( s e q 1000 ) d o e c h o “ a b c a s d f a s d f a s d f s a d f w e r w e i r o “ (seq 1000) do echo “abc asdf as df asd f sadf werweiro “ (seq1000)doechoabcasdfasdfasdfsadfwerweiroi > file_${i}
done
shell> ./file_gen.sh
shell> ls -lh

-rw-r–r– 1 root root 40 Dec 25 10:14 file_962
-rw-r–r– 1 root root 40 Dec 25 10:14 file_963
-rw-r–r– 1 root root 40 Dec 25 10:14 file_964
-rw-r–r– 1 root root 40 Dec 25 10:14 file_965
-rw-r–r– 1 root root 40 Dec 25 10:14 file_966
-rw-r–r– 1 root root 40 Dec 25 10:14 file_967

上面生成大量小文件,上传这些小文件:
shell> hadoop fs -put file* /tmp/carl2/

wordcount使用TextInputFormat方式,小文件一个个处理:
shell> hadoop jar hadoop-mapreduce-examples-2.0.0-cdh4.4.0.jar wordcount /tmp/carl2/ /tmp/carl_result3/

13/12/25 10:34:25 INFO input.FileInputFormat: Total input paths to process : 1000
13/12/25 10:34:27 INFO mapreduce.JobSubmitter: number of splits:1000

13/12/25 10:34:33 INFO mapreduce.Job: map 0% reduce 0%
13/12/25 10:34:40 INFO mapreduce.Job: map 1% reduce 0%
13/12/25 10:34:41 INFO mapreduce.Job: map 2% reduce 0%

13/12/25 10:40:56 INFO mapreduce.Job: map 100% reduce 94%
13/12/25 10:40:57 INFO mapreduce.Job: map 100% reduce 100%

可以看到,生成了1000个map任务,总耗时超过6分钟!

multifilewc使用CombineInputFormat方式,没有设置maxSplitSize的情况下,所有小文件会汇总成一个Split。
shell> hadoop jar hadoop-mapreduce-examples-2.0.0-cdh4.4.0.jar multifilewc /tmp/carl2/ /tmp/carl_result4/

13/12/25 10:42:04 INFO input.FileInputFormat: Total input paths to process : 1000
13/12/25 10:42:07 INFO mapreduce.JobSubmitter: number of splits:1

13/12/25 10:42:12 INFO mapreduce.Job: map 0% reduce 0%
13/12/25 10:42:19 INFO mapreduce.Job: map 100% reduce 0%
13/12/25 10:42:24 INFO mapreduce.Job: map 100% reduce 25%
13/12/25 10:42:25 INFO mapreduce.Job: map 100% reduce 31%
13/12/25 10:42:27 INFO mapreduce.Job: map 100% reduce 38%
13/12/25 10:42:28 INFO mapreduce.Job: map 100% reduce 56%
13/12/25 10:42:29 INFO mapreduce.Job: map 100% reduce 63%
13/12/25 10:42:30 INFO mapreduce.Job: map 100% reduce 69%
13/12/25 10:42:31 INFO mapreduce.Job: map 100% reduce 81%
13/12/25 10:42:32 INFO mapreduce.Job: map 100% reduce 88%
13/12/25 10:42:33 INFO mapreduce.Job: map 100% reduce 100%

可以看到,只用一个map任务进行处理,大量小文件可以使用网络迅速的汇总,总耗时不到30秒!

测试的结果可以大致看出,针对大量小文件,使用CombineInputFormat具有较大优势。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Hive是一个基于Hadoop的数据仓库工具,它可以将SQL语句转化为MapReduce任务进行处理。在Hive中,每个SQL语句都会被转换为一个或多个MapReduce任务,具体的转换方式由Hive的执行引擎完成。在MapReduce任务执行过程中,Hive会把数据从HDFS中读取到内存中,进行各种计算和操作,最后再将结果写回到HDFS中。 在Hive中,MapReduce任务的执行过程可以分为以下几个步骤: 1. 输入数据的读取:Hive会从HDFS中读取输入数据,并按照指定的格式进行解析和转换,生成相应的键值对。 2. Map阶段的处理:Hive会将输入数据传给Map函数,对每个键值对进行处理,并输出中间结果。在Map过程中,Hive会执行用户定义的SQL语句,进行各种计算和操作。 3. Shuffle阶段的处理:Hive会将Map输出的中间结果按照键进行分组,并将相同键的值合并成一个列表。这个过程叫做Shuffle。 4. Reduce阶段的处理:Hive会将Shuffle输出的结果传给Reduce函数,对每个键值对进行处理,并输出最终结果。在Reduce过程中,Hive会执行用户定义的SQL语句,进行各种计算和操作。 5. 输出数据的写入:Hive会将Reduce输出的结果写回到HDFS中,生成最终的结果文件。 总的来说,HiveMapReduce主要是用来处理大规模的数据集,它通过将SQL转化为MapReduce任务,利用Hadoop分布式计算的能力,实现了高效的数据处理和分析。

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值