web-dev-qa-db-ja.com

Hadoopプロセスはどのようにブロック境界を越えてレコードを分割しますか?

による Hadoop - The Definitive Guide

FileInputFormatsが定義する論理レコードは、通常、HDFSブロックにうまく収まりません。たとえば、TextInputFormatの論理レコードは行であり、HDFSの境界を頻繁に超えます。これはプログラムの機能には関係ありません。たとえば、行の欠落や破損はありませんが、データローカルマップ(つまり、ホストと同じホストで実行されているマップ)入力データ)は、いくつかのリモート読み取りを実行します。これが引き起こすわずかなオーバーヘッドは、通常は重要ではありません。

レコード行が2つのブロック(b1とb2)に分割されているとします。最初のブロック(b1)を処理するマッパーは、最後の行にEOLセパレータがないことに気付き、次のデータブロック(b2)から残りの行をフェッチします。

2番目のブロック(b2)を処理するマッパーは、最初のレコードが不完全であり、ブロック(b2)の2番目のレコードから処理を開始する必要があるとどのように判断しますか?

116
Praveen Sripati

興味深い質問ですが、詳細についてはコードを見て時間を費やしました。これが私の考えです。分割は_InputFormat.getSplits_によってクライアントによって処理されるため、FileInputFormatを見ると次の情報が得られます。

  • 各入力ファイルについて、ファイル長、ブロックサイズを取得し、max(minSize, min(maxSize, blockSize))として分割サイズを計算します。ここで、maxSizeは_mapred.max.split.size_に対応し、minSizeは_mapred.min.split.size_。
  • 上記で計算した分割サイズに基づいて、ファイルを異なるFileSplitsに分割します。ここで重要なのは、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をインスタンス化しようとします。 2つのケースがあります。
  • CompressionCodecが定義されている場合、このコーデックは境界の処理を担当します。おそらくあなたの質問には関係ありません。
  • ただし、コーデックがない場合は、興味深いことになります。startInputSplitが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で初期化されました。
  • 開始200-200 + 128 = 128Mb、長さ64Mbで初期化されたスプリット3。
  • 開始200-200 + 192 = 192Mb、長さ8Mbで初期化されたスプリット4。
  • マッパーAはスプリット1を処理し、startは0なので最初の行をスキップせず、64Mbの制限を超える行全体を読み取るため、リモート読み取りが必要です。
  • マッパーBはスプリット2を処理します。開始は!= 0なので、64Mb-1バイトの後の最初の行をスキップします。これは、スプリット2にある100Mbの行1の終わりに対応します。残りの72Mbをリモートで読み取ります。
  • マッパーCはスプリット3を処理します。startは!= 0なので、128Mb-1byteの後の最初の行をスキップします。これは200Mbの行2の終わりに対応し、ファイルの終わりなので何もしません。
  • マッパーDは、192Mb-1バイトの後に改行を探すことを除いて、マッパーCと同じです。
155
Charles Menguy

Map Reduceアルゴリズムは、ファイルの物理ブロックでは機能しません。論理的な入力分割で機能します。入力分割は、レコードが書き込まれた場所によって異なります。レコードは2つのマッパーにまたがることがあります。

[〜#〜] hdfs [〜#〜]が設定され、非常に大きなファイルを大きなブロック(たとえば、128MB)に分割し、これらのブロックのコピーを3つ保存します。クラスター内の異なるノード。

HDFSはこれらのファイルの内容を認識しません。レコードはBlock-aで開始された可能性がありますが、そのレコードの終わりはBlockに存在する可能性があります-b

この問題を解決するために、Hadoopは、入力分割と呼ばれるファイルブロックに格納されたデータの論理表現を使用します。 MapReduceジョブクライアントがinput splitsを計算すると、ブロック内の最初のレコード全体がどこから始まり、ブロック内の最後のレコードがどこまで終わるかがわかります

キーポイント:

ブロック内の最後のレコードが不完全な場合、入力分割には次のブロックの位置情報と、レコードを完了するために必要なデータのバイトオフセットが含まれます。

下の図をご覧ください。

enter image description here

これをご覧ください 記事 および関連するSEの質問: Hadoop/HDFSファイル分割について

詳細は documentation から読むことができます

Map-Reduceフレームワークは、ジョブのInputFormatに依存して次のことを行います。

  1. ジョブの入力仕様を検証します。
  2. 入力ファイルを論理InputSplitsに分割し、それぞれを個別のマッパーに割り当てます。
  3. 各InputSplitは、処理のために個々のマッパーに割り当てられます。 SplitはTupleです。 InputSplit[] getSplits(JobConf job,int numSplits)は、これらのことを処理するAPIです。

FileInputFormat 、これはInputFormat実装getSplits()メソッドを拡張します。 grepcode でこのメソッドの内部を見てください

16
Ravindra babu

私はそれを次のように見ます:InputFormatは、データの性質を考慮してデータを論理的な分割に分割する責任があります。
ジョブに大きなレイテンシを追加する可能性はありますが、それを妨げるものはありません-必要な分割サイズ境界の周​​りのすべてのロジックと読み取りは、ジョブトラッカーで行われます。
最も単純なレコード対応入力フォーマットはTextInputFormatです。それは次のように機能しています(コードから理解している限り)-入力形式は、行に関係なくサイズで分割を作成しますが、LineRecordReaderは常に:
a)最初のスプリットではない場合、スプリット(またはその一部)の最初の行をスキップします
b)最後の分割の境界の後の1行を読み取ります(データが使用可能な場合、最後の分割ではありません)。

7
David Gruzman

私が理解したことから、最初のブロックに対してFileSplitが初期化されると、デフォルトのコンストラクターが呼び出されます。したがって、startとlengthの値は最初はゼロです。最初のブロックの処理が終了するまでに、最後の行が不完全な場合、長さの値は分割の長さより大きくなり、次のブロックの最初の行も読み取ります。このため、最初のブロックの開始値はゼロより大きくなり、この条件下では、LineRecordReaderは2番目のブロックの最初の行をスキップします。 ( source を参照)

最初のブロックの最後の行が完了した場合、長さの値は最初のブロックの長さと等しくなり、2番目のブロックの開始の値はゼロになります。その場合、LineRecordReaderは最初の行をスキップせず、最初から2番目のブロックを読み取ります。

理にかなっていますか?

3
aa8y

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は各分割ごとに1行余分に読み込み(現在の分割の最後に、次の分割の次の行を読み込む)、最初の分割でない場合は最初の行が破棄されると信じています。行レコードが失われたり不完全になることはありません

1
Shenghai.Geng

マッパーは通信する必要はありません。ファイルブロックはHDFSにあり、現在のマッパー(RecordReader)は、行の残りの部分を持つブロックを読み取ることができます。これはバックグラウンドで発生します。

0
user3507308