Hadoop流程记录如何跨块边界分割?

时间:2013-01-12 07:10:57

标签: hadoop split mapreduce block hdfs

根据Hadoop - The Definitive Guide

  

FileInputFormats定义的逻辑记录通常不适合HDFS块。例如,TextInputFormat的逻辑记录是行,它们将经常跨越HDFS边界。这与你的程序的功能没有关系 - 例如,线路不会丢失或损坏 - 但值得了解,因为它确实意味着数据本地地图(即,与他们在同一主机上运行的地图)输入数据)将执行一些远程读取。这导致的轻微开销通常不显着。

假设记录行分为两个块(b1和b2)。处理第一个块(b1)的映射器将注意到最后一行没有EOL分隔符,并从下一个数据块中取出剩余的行(b2)。

映射器如何处理第二个块(b2)如何确定第一个记录是不完整的并且应该从块(b2)中的第二个记录开始处理?

6 个答案:

答案 0 :(得分:152)

有趣的问题,我花了一些时间查看代码以获取详细信息,这是我的想法。客户端由InputFormat.getSplits处理拆分,因此查看FileInputFormat会提供以下信息:

  • 对于每个输入文件,获取文件长度,块大小并将分割大小计算为max(minSize, min(maxSize, blockSize)),其中maxSize对应mapred.max.split.sizeminSize为{{1} }}。
  • 根据上面计算的拆分大小将文件划分为不同的mapred.min.split.size s。这里重要的是每个FileSplit都使用与输入文件中的偏移量相对应的FileSplit参数进行初始化。此时仍然没有处理线路。代码的相关部分如下所示:

    start

之后,如果您查看由while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) { int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining); splits.add(new FileSplit(path, length-bytesRemaining, splitSize, blkLocations[blkIndex].getHosts())); bytesRemaining -= splitSize; } 定义的LineRecordReader,那就是处理这些行的位置:

  • 初始化TextInputFormat时,它会尝试实例化LineRecordReader,这是一个抽象,可以读取LineReader上的行。有两种情况:
  • 如果定义了FSDataInputStream,则此编解码器负责处理边界。可能与您的问题无关。
  • 如果没有编解码器,那就是感兴趣的地方:如果CompressionCodec的{​​{1}}不是0,那么你回溯1个字符,然后跳过您遇到的第一行由\ n或\ r \ n(Windows)标识!回溯非常重要,因为如果您的行边界与拆分边界相同,这可以确保您不会跳过有效行。以下是相关代码:

    start

因为分割是在客户端计算的,所以映射器不需要按顺序运行,每个映射器都知道它是否需要丢弃第一行。

所以基本上如果你在同一个文件中每个100Mb有2行,并且为了简化,我们说分割大小是64Mb。然后,当计算输入拆分时,我们将得到以下情形:

  • 拆分1,包含此块的路径和主机。初始化为200-200 = 0Mb,长度为64Mb。
  • 拆分2在开始时初始化200-200 + 64 = 64Mb,长度64Mb。
  • 拆分3在开始时初始化200-200 + 128 = 128Mb,长度64Mb。
  • 拆分4在开始时初始化200-200 + 192 = 192Mb,长度8Mb。
  • 映射器A将处理拆分1,start为0因此不要跳过第一行,并读取超出64Mb限制的完整行,因此需要远程读取。
  • 映射器B将处理分裂2,start是!= 0因此跳过64Mb-1byte之后的第一行,这对应于100Mb处的第1行的末尾仍然处于分割2中,我们在分割中有28Mb的行2,所以远程读取剩余的72Mb。
  • 映射器C将处理分裂3,start为!= 0,因此跳过128Mb-1byte之后的第一行,这对应于200Mb处的第2行的结尾,这是文件的结尾所以不要做任何事情。
  • Mapper D与mapper C相同,只是它在192Mb-1byte之后查找换行符。

答案 1 :(得分:15)

Map Reduce 算法不适用于文件的物理块。它适用于逻辑输入拆分。输入拆分取决于记录的写入位置。记录可能跨越两个Mappers。

HDFS 的设置方式,它将非常大的文件分解为大块(例如,测量128MB),并将这些块的三个副本存储在群集中的不同节点上。

HDFS无法了解这些文件的内容。记录可能已在 Block-a 中启动,但该记录的结尾可能出现在 Block-b

为了解决这个问题,Hadoop使用存储在文件块中的数据的逻辑表示,称为输入拆分。当MapReduce作业客户端计算输入拆分时, 会计算出块中第一个完整记录的开始位置以及块中最后一个记录的结束位置

关键点:

如果块中的最后一条记录不完整,则输入拆分包括下一个块的位置信息和完成记录所需数据的字节偏移。

看看下图。

enter image description here

查看此article及相关的SE问题:About Hadoop/HDFS file splitting

可以从documentation

了解更多详情

Map-Reduce框架依赖于作业的InputFormat:

  1. 验证作业的输入规范。
  2. 将输入文件拆分为逻辑InputSplits,然后将每个输入文件分配给单个Mapper。
  3. 然后将每个InputSplit分配给单个Mapper进行处理。 Split可能是元组InputSplit[] getSplits(JobConf job,int numSplits)是处理这些事情的API。
  4. FileInputFormat,它扩展了InputFormat实施的getSplits()方法。在grepcode

    查看此方法的内部结构

答案 2 :(得分:7)

我认为如下:InputFormat负责将数据拆分为逻辑分割,同时考虑到数据的性质。没有什么可以阻止它这样做,虽然它可以增加工作的显着延迟 - 所有的逻辑和读取所需的分割大小边界将发生在jobtracker中。
最简单的记录感知输入格式是TextInputFormat。它的工作原理如下(据我从代码中理解) - 输入格式按大小创建拆分,无论线条如何,但LineRecordReader始终如下:
a)如果不是第一次分割,则跳过分割(或部分分割)中的第一行 b)在分割的边界之后读取一行(如果数据可用,那么它不是最后一次分割)。

答案 3 :(得分:3)

根据我的理解,当为第一个块初始化FileSplit时,将调用默认构造函数。因此,start和length的值最初为零。在第一个块的处理结束时,如果最后一行不完整,那么长度的值将大于分割的长度,并且它也将读取下一个块的第一行。由于这个原因,第一个块的start值将大于零,在这种情况下,LineRecordReader将跳过第二个块的第一行。 (见source

如果第一个块的最后一行完成,则length的值将等于第一个块的长度,第二个块的start的值将为零。在这种情况下,LineRecordReader不会跳过第一行并从头开始读取第二个块。

有道理吗?

答案 4 :(得分:1)

从LineRecordReader.java的hadoop源代码构造函数:我找到一些评论:

// If this is not the first split, we always throw away first record
// because we always (except the last split) read one extra line in
// next() method.
if (start != 0) {
  start += in.readLine(new Text(), 0, maxBytesToConsume(start));
}
this.pos = start;

从此我相信hadoop将为每个分割读取一个额外的行(在当前分割结束时,在下一个分割中读取下一行),如果不是先分割,则第一行将被丢弃。所以没有行记录会​​丢失和不完整

答案 5 :(得分:0)

地图制作者无需沟通。文件块是HDFS,当前映射器(RecordReader)可以读取具有该行剩余部分的块。这发生在幕后。