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


119

根据 Hadoop - The Definitive Guide

FileInputFormats定义的逻辑记录通常不能整齐地放入HDFS块中。例如,TextInputFormat的逻辑记录是行,这些行将经常跨越HDFS边界。这与程序的功能无关(例如,行不会丢失或断线),但是这是值得了解的,因为这确实意味着数据本地映射(即与它们在同一主机上运行的映射)输入数据)将执行一些远程读取。造成的轻微开销通常并不重要。

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

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

Answers:


160

有趣的问题,我花了一些时间查看代码的详细信息,这是我的想法。拆分由客户端通过来处理InputFormat.getSplits,因此查看FileInputFormat可以得到以下信息:

  • 对于每个输入文件,获取文件长度,块的大小,并计算分割尺寸max(minSize, min(maxSize, blockSize)),其中maxSize对应于mapred.max.split.sizeminSizemapred.min.split.size
  • FileSplit根据上面计算出的分割大小将文件分成不同的。这里重要的是,每个参数FileSplit都使用与start输入文件中的offset对应的参数进行初始化。那时仍然没有处理这些行。代码的相关部分如下所示:

    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它会尝试实例化a LineReader,这是一种能够读取行上方的抽象FSDataInputStream。有两种情况:
  • 如果有CompressionCodec定义,则此编解码器负责处理边界。可能与您的问题无关。
  • 但是,如果没有编解码器,那就很有趣:如果start您的InputSplit的不同于0,则您回溯1个字符,然后跳过遇到的由\ n或\ r \ n标识的第一行(Windows)!回溯很重要,因为如果行边界与分割边界相同,这可以确保您不会跳过有效行。以下是相关代码:

    if (codec != null) {
       in = new LineReader(codec.createInputStream(fileIn), job);
       end = Long.MAX_VALUE;
    } else {
       if (start != 0) {
         skipFirstLine = true;
         --start;
         fileIn.seek(start);
       }
       in = new LineReader(fileIn, job);
    }
    if (skipFirstLine) {  // skip first line and re-establish "start".
      start += in.readLine(new Text(), 0,
                        (int)Math.min((long)Integer.MAX_VALUE, end - start));
    }
    this.pos = 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,开始为0,所以不要跳过第一行,而是读取超出64Mb限制的整行,因此需要远程读取。
  • 映射器B将处理拆分2,开始为!= 0,因此跳过64Mb-1byte之后的第一行,这对应于拆分2中仍在拆分2中的行1的末尾100Mb,因此拆分2中有28Mb的行,因此远程读取剩余的72Mb。
  • 映射器C将处理拆分3,开始为!= 0,因此跳过128Mb-1byte之后的第一行,该行对应于200Mb的第2行的末尾,也就是文件的末尾,因此请勿执行任何操作。
  • 映射器D与映射器C相同,只不过它在192Mb-1byte之后寻找换行符。

另外@PraveenSripati值得一提的是,在LineReader.readLine函数中处理了边界在\ r \ n返回中\ r的边缘情况,我认为这与您的问题无关,但可以在需要时添加更多详细信息。
Charles Menguy

假设有两行输入的确切大小为64MB,因此InputSplits恰好发生在行边界处。因此,将映射器总是忽略第二块,因为开始行= 0!
普利文Sripati的

6
@PraveenSripati在这种情况下,第二个映射器将看到start!= 0,因此回溯1个字符,这使您回到第一行的\ n之前,然后跳至下一个\ n。因此它将跳过第一行,但按预期处理第二行。
2013年

@CharlesMenguy是否有可能以某种方式跳过文件的第一行?具体来说,我在第一行中有key = 1,值a,然后在文件的某处还有两行具有相同key的键,key = 1,val = b,key = 1,val = c。事实是,我的减速器获得{1,[b,c]}和{1,[a]},而不是{1,[a,b,c]}。如果我在文件开头添加新行,则不会发生这种情况。主席先生,可能是什么原因?
神户湾基诺比2014年

@CharlesMenguy如果HDFS上的文件是二进制文件(而不是文本文件,在其中\r\n, \n代表记录截断),该怎么办?
CᴴᴀZ

17

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

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

HDFS不了解这些文件的内容。记录可能已在块a中开始,但该记录的结尾可能出现在块b中

为了解决此问题,Hadoop使用存储在文件块中的数据的逻辑表示形式,称为输入拆分。当MapReduce作业客户端计算输入拆分时它将找出块中第一个完整记录的起始位置以及块中最后一个记录的终止位置

重点:

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

看下图。

在此处输入图片说明

看一下本文和相关的SE问题:关于Hadoop / HDFS文件拆分

可以从文档中了解更多详细信息

Map-Reduce框架依靠作业的InputFormat来:

  1. 验证作业的输入规范。
  2. 将输入文件拆分为逻辑InputSplit,然后将每个分配给一个单独的Mapper。
  3. 然后将每个InputSplit分配给一个单独的Mapper进行处理。拆分可能是元组InputSplit[] getSplits(JobConf job,int numSplits)是负责处理这些事情的API。

FileInputFormat,它扩展了InputFormatImplemented getSplits()方法。在grepcode上查看此方法的内部知识


7

我看到如下:InputFormat负责将数据拆分为逻辑拆分,同时考虑到数据的性质。
尽管这样做会增加作业的显着延迟,但没有任何事情可以阻止它-作业跟踪器中会发生所有逻辑和所需分割大小边界的读取。
最简单的记录识别输入格式是TextInputFormat。它的工作方式如下(据我从代码中了解)-输入格式按大小创建拆分,而不考虑行,但LineRecordReader始终:
a)跳过拆分(或其一部分)中的第一行(如果不是)第一个拆分
b)最终在拆分边界之后读取一行(如果有数据,则不是最后一个拆分)。


Skip first line in the split (or part of it), if it is not the first split-如果非第一块中的第一条记录已完成,则不确定此逻辑将如何工作。
Praveen Sripati

据我看到的代码-每个拆分读取了它的内容+下一行。因此,如果换行符不在块边界上-可以。当换行符正好在块边界上时,如何处理这种情况-必须理解-我将阅读更多代码
David Gruzman 2013年

3

据我了解,当FileSplit为第一个块初始化时,将调用默认构造函数。因此,开始和长度的值最初为零。到第一个块的处理结束时,如果最后一行不完整,则length的值将大于拆分的长度,并且还将读取下一个块的第一行。因此,第一个块的开始值将大于零,并且在这种情况下,LineRecordReader将跳过第二个块的第一行。(请参阅源代码

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

说得通?


2
在这种情况下,当特定块中的最后一行未完成时,映射器必须相互通信并按顺序处理这些块。不知道这是否是它的工作方式。
Praveen Sripati

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会为每个拆分读取一条额外的行(在当前拆分的末尾,在下一个拆分中读取下一行),如果不是第一次拆分,则第一行将被丢弃。这样就不会丢失任何行记录并且记录不完整


0

映射器不必通信。文件块位于HDFS中,当前的映射器(RecordReader)可以读取具有该行其余部分的块。这发生在幕后。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.