Hadoop 프로세스 레코드는 블록 경계에서 어떻게 분할됩니까?


119

에 따르면 Hadoop - The Definitive Guide

FileInputFormats가 정의하는 논리적 레코드는 일반적으로 HDFS 블록에 깔끔하게 맞지 않습니다. 예를 들어, TextInputFormat의 논리 레코드는 HDFS 경계를 더 자주 교차하는 라인입니다. 이것은 프로그램의 기능에 아무런 영향을 미치지 않습니다. 예를 들어 선이 누락되거나 끊어지지는 않습니다. 그러나 데이터 로컬 맵 (즉, 동일한 호스트에서 실행되는 맵 입력 데이터)는 일부 원격 읽기를 수행합니다. 이로 인해 발생하는 약간의 오버 헤드는 일반적으로 중요하지 않습니다.

레코드 라인이 두 블록 (b1 및 b2)으로 분할되었다고 가정합니다. 첫 번째 블록 (b1)을 처리하는 매퍼는 마지막 줄에 EOL 구분 기호가 없음을 인식하고 다음 데이터 블록 (b2)에서 나머지 줄을 가져옵니다.

두 번째 블록 (b2)을 처리하는 매퍼는 첫 번째 레코드가 불완전하며 블록 (b2)의 두 번째 레코드부터 처리해야한다고 어떻게 결정합니까?

답변:


160

흥미로운 질문은 코드에서 세부 사항을 보는데 시간을 보냈으며 여기에 내 생각이 있습니다. 분할은 클라이언트가에서 처리 InputFormat.getSplits하므로 FileInputFormat을 살펴보면 다음 정보가 제공됩니다.

  • 각 입력 파일에는 파일의 길이, 블록 크기를 얻을하고 분할 사이즈 계산 max(minSize, min(maxSize, blockSize))곳에 maxSize대응하는 mapred.max.split.sizeminSize이다 mapred.min.split.size.
  • FileSplit위에서 계산 된 분할 크기에 따라 파일을 다른 s 로 분할합니다. 여기서 중요한 것은 각각 FileSplitstart입력 파일의 오프셋에 해당 하는 매개 변수 로 초기화 된다는 입니다 . 그 시점에서 여전히 라인 처리가 없습니다. 코드의 관련 부분은 다음과 같습니다.

    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정의,이 코덱은 경계를 처리 할 책임이있다. 아마도 귀하의 질문과 관련이 없습니다.
  • 가지 흥미있는 곳이다 그러나 어떤 코덱이없는 경우 : 경우에 start당신이의 InputSplit0이 아닌 다른이, 당신은 1 개 문자를 역 추적 한 후하여 식별 발생하는 첫 번째 줄을 건너 \ 또는 \ R \ N (윈도우) N을 ! 역 추적은 선 경계가 분할 경계와 동일한 경우 유효한 선을 건너 뛰지 않도록하기 때문에 중요합니다. 다음은 관련 코드입니다.

    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의 두 줄이 있고 단순화하기 위해 분할 크기가 64Mb라고 가정 해 보겠습니다. 그런 다음 입력 분할이 계산되면 다음과 같은 시나리오가됩니다.

  • 이 블록에 대한 경로와 호스트를 포함하는 분할 1. 시작 200-200 = 0Mb, 길이 64Mb에서 초기화됩니다.
  • 시작 200-200 + 64 = 64Mb, 길이 64Mb에서 분할 2가 초기화되었습니다.
  • 시작 200-200 + 128 = 128Mb, 길이 64Mb에서 분할 3이 초기화되었습니다.
  • 시작 200-200 + 192 = 192Mb, 길이 8Mb에서 분할 4가 초기화되었습니다.
  • 매퍼 A는 분할 1을 처리하고 시작은 0이므로 첫 번째 줄을 건너 뛰지 말고 64Mb 제한을 초과하는 전체 줄을 읽으므로 원격 읽기가 필요합니다.
  • 매퍼 B는 분할 2를 처리하고 시작은! = 0이므로 64Mb-1byte 이후의 첫 번째 줄은 건너 뜁니다. 이는 여전히 분할 2에있는 100Mb에서 행 1의 끝에 해당합니다. 분할 2에는 28Mb의 행이 있습니다. 원격은 나머지 72Mb를 읽습니다.
  • 매퍼 C는 분할 3을 처리하고 시작은! = 0이므로 128Mb-1byte 뒤의 첫 번째 줄은 건너 뛰고, 이는 파일의 끝인 200Mb에서 줄 2의 끝에 해당하므로 아무것도하지 마십시오.
  • 매퍼 D는 192Mb-1byte 이후에 개행을 찾는 것을 제외하고 매퍼 C와 동일합니다.

또한 @PraveenSripati 경계가 \ r에서 \ r에있는 경계 케이스가 LineReader.readLine함수 에서 처리된다는 점을 언급 할 가치가 있습니다. 질문과 관련이 없다고 생각하지만 필요한 경우 더 많은 세부 정보를 추가 할 수 있습니다.
Charles Menguy

입력에 정확히 64MB의 두 줄이 있다고 가정하고 InputSplits는 줄 경계에서 정확히 발생합니다. 그래서, 매퍼는 항상 시작하기 때문에 두 번째 블록의 라인을 무시 = 0!
프라 빈 Sripati

6
@PraveenSripati이 경우 두 번째 매퍼는 start! = 0을 볼 수 있으므로 1 문자를 역 추적하여 첫 번째 줄의 \ n 바로 앞으로 돌아가서 다음 \ n으로 건너 뜁니다. 따라서 첫 번째 줄은 건너 뛰지 만 예상대로 두 번째 줄은 처리합니다.
Charles Menguy

@CharlesMenguy 파일의 첫 번째 줄이 어떻게 든 건너 뛸 수 있습니까? 구체적으로, 첫 번째 줄에 key = 1, 값 a가 있고 파일 어딘가에 key = 1, val = b 및 key = 1, val = c 같은 키가있는 두 줄이 더 있습니다. 문제는 내 감속기가 {1, [a, b, c]} 대신 {1, [b, c]} 및 {1, [a]}를 얻는다는 것입니다. 파일 시작 부분에 새 줄을 추가하면 이런 일이 발생하지 않습니다. 그 이유가 무엇일까요?
Kobe-Wan Kenobi

@CharlesMenguy HDFS의 파일이 바이너리 파일 ( \r\n, \n레코드 잘림 을 나타내는 텍스트 파일과 반대)이면 어떻게됩니까?
CᴴᴀZ

17

Map Reduce 알고리즘은 파일의 물리적 블록에서 작동하지 않습니다. 논리적 입력 분할에서 작동합니다. 입력 분할은 레코드가 작성된 위치에 따라 다릅니다. 레코드는 두 매퍼에 걸쳐있을 수 있습니다.

HDFS 가 설정된 방식 은 매우 큰 파일을 큰 블록 (예 : 128MB 측정)으로 나누고 클러스터의 서로 다른 노드에 이러한 블록의 복사본 세 개를 저장합니다.

HDFS는 이러한 파일의 내용을 인식하지 못합니다. 레코드는 Block-a 에서 시작 되었지만 해당 레코드의 끝은 Block-b 에있을 수 있습니다 .

이 문제를 해결하기 위해 Hadoop은 입력 분할이라고하는 파일 블록에 저장된 데이터의 논리적 표현을 사용합니다. 맵리 듀스 작업 클라이언트가 계산하면 입력 분할 , 그것은 블록의 첫 번째 전체 기록이 시작되고 어디 어디 파악 블록 끝의 마지막 레코드 .

요점 :

블록의 마지막 레코드가 불완전한 경우 입력 분할에는 다음 블록에 대한 위치 정보와 레코드를 완료하는 데 필요한 데이터의 바이트 오프셋이 포함됩니다.

아래 다이어그램을 살펴보십시오.

여기에 이미지 설명 입력

기사 및 관련 SE 질문을 살펴보십시오 : Hadoop / HDFS 파일 분할 정보

자세한 내용은 문서 에서 읽을 수 있습니다.

Map-Reduce 프레임 워크는 작업의 InputFormat을 사용하여 다음을 수행합니다.

  1. 작업의 입력 사양을 확인합니다.
  2. 입력 파일을 논리적 InputSplits로 분할 한 다음 각각이 개별 매퍼에 할당됩니다.
  3. 그런 다음 각 InputSplit은 처리를 위해 개별 매퍼에 할당됩니다. Split은 tuple 일 수 있습니다 . InputSplit[] getSplits(JobConf job,int numSplits)는 이러한 것들을 처리하는 API입니다.

InputFormat구현 된 getSplits() 메서드 를 확장 하는 FileInputFormat . grepcode 에서이 메소드의 내부를 살펴보십시오.


7

다음과 같이 볼 수 있습니다. InputFormat은 데이터의 특성을 고려하여 데이터를 논리적 분할로 분할합니다.
작업에 상당한 대기 시간을 추가 할 수는 있지만이를 방지하는 것은 없습니다. 원하는 분할 크기 경계에 대한 모든 논리와 읽기가 jobtracker에서 발생합니다.
가장 간단한 레코드 인식 입력 형식은 TextInputFormat입니다. 다음과 같이 작동합니다 (코드에서 이해하는 한)-입력 형식은 줄에 관계없이 크기별로 분할을 생성하지만 LineRecordReader는 항상 :
a) 그렇지 않은 경우 분할 (또는 일부)에서 첫 번째 줄을 건너 뜁니다. 첫 번째 분할
b) 마지막 분할 경계 뒤의 한 줄을 읽습니다 (데이터를 사용할 수있는 경우 마지막 분할이 아님).


Skip first line in the split (or part of it), if it is not the first split-첫 번째가 아닌 블록의 첫 번째 레코드가 완료되면이 논리가 어떻게 작동하는지 확실하지 않습니다.
Praveen Sripati 2013 년

내가 코드를 보는 한-각 분할은 + 다음 줄이있는 것을 읽습니다. 따라서 줄 바꿈이 블록 경계에 있지 않으면 괜찮습니다. 줄 바꿈이 블록에 정확히 때 사건을 처리하는 방법을 정확하게 바인딩 - 이해되어야한다 - 나는 조금 더 많은 코드를 읽
데이비드 Gruzman

3

내가 이해 한 바에 따르면 FileSplit이 첫 번째 블록에 대해 초기화되면 기본 생성자가 호출됩니다. 따라서 시작 및 길이 값은 초기에 0입니다. 첫 번째 블록의 처리가 끝날 때 마지막 줄이 불완전하면 길이 값이 분할 길이보다 커지고 다음 블록의 첫 번째 줄도 읽습니다. 이로 인해 첫 번째 블록의 시작 값은 0보다 크고이 조건 LineRecordReader에서 두 번째 블록의 첫 번째 줄을 건너 뜁니다. ( 출처 참조 )

첫 번째 블록의 마지막 줄이 완성 된 경우 길이 값은 첫 번째 블록의 길이와 같고 두 번째 블록의 시작 값은 0이됩니다. 이 경우 LineRecordReader첫 번째 줄을 건너 뛰지 않고 처음부터 두 번째 블록을 읽습니다.

말이된다?


2
이 시나리오에서 매퍼는 특정 블록의 마지막 줄이 완료되지 않은 경우 서로 통신하고 블록을 순서대로 처리해야합니다. 이것이 작동 방식인지 확실하지 않습니다.
Praveen Sripati 2013 년

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)는 라인의 나머지 부분이있는 블록을 읽을 수 있습니다. 이것은 장면 뒤에서 발생합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.