web-dev-qa-db-ja.com

Hadoopジョブの既存の出力パスを上書き/再利用する方法とアジャン

Hadoopジョブを毎日実行するときに、既存の出力ディレクトリを上書き/再利用したい。実際、出力ディレクトリには、毎日のジョブ実行結果の要約された出力が格納されます。同じ出力ディレクトリを指定すると、「出力ディレクトリが既に存在します」というエラーが発生します。

この検証をバイパスする方法は?

22
yogesh

ジョブを実行する前にディレクトリを削除するのはどうですか?

シェルを介してこれを行うことができます:

hadoop fs -rmr /path/to/your/output/

またはJava API:

// configuration should contain reference to your namenode
FileSystem fs = FileSystem.get(new Configuration());
// true stands for recursively deleting the folder you gave
fs.delete(new Path("/path/to/your/output"), true);
16
Thomas Jungblut

ジャンブルートの答えはあなたの直接的な解決策です。私が個人的に削除する自動化されたプロセスを信頼することは決してないので、別の方法を提案します。

上書きしようとする代わりに、実行時間を含めて、ジョブの出力名を動的にすることをお勧めします。

/path/to/your/output-2011-10-09-23-04/」のようなもの。このようにして、再訪問が必要になった場合に備えて、古いジョブの出力を保持できます。毎日10件以上のジョブを実行する私のシステムでは、出力を/output/job1/2011/10/09/job1out/part-r-xxxxx/output/job1/2011/10/10/job1out/part-r-xxxxxなどに構成します。 。

11
Donald Miner

HadoopのTextInputFormat(使用していると思います)では、既存のディレクトリを上書きできません。おそらく、あなた(そしてあなたのクラスター)が非常に懸命に取り組んだ何かを誤って削除してしまったことを発見する苦痛を許してしまうでしょう。

ただし、出力フォルダーをジョブで上書きしたい場合は、TextOutputFormatを次のように少し変更するのが最もクリーンな方法だと思います。

_public class OverwriteTextOutputFormat<K, V> extends TextOutputFormat<K, V>
{
      public RecordWriter<K, V> 
      getRecordWriter(TaskAttemptContext job) throws IOException, InterruptedException 
      {
          Configuration conf = job.getConfiguration();
          boolean isCompressed = getCompressOutput(job);
          String keyValueSeparator= conf.get("mapred.textoutputformat.separator","\t");
          CompressionCodec codec = null;
          String extension = "";
          if (isCompressed) 
          {
              Class<? extends CompressionCodec> codecClass = 
                      getOutputCompressorClass(job, GzipCodec.class);
              codec = (CompressionCodec) ReflectionUtils.newInstance(codecClass, conf);
              extension = codec.getDefaultExtension();
          }
          Path file = getDefaultWorkFile(job, extension);
          FileSystem fs = file.getFileSystem(conf);
          FSDataOutputStream fileOut = fs.create(file, true);
          if (!isCompressed) 
          {
              return new LineRecordWriter<K, V>(fileOut, keyValueSeparator);
          } 
          else 
          {
              return new LineRecordWriter<K, V>(new DataOutputStream(codec.createOutputStream(fileOut)),keyValueSeparator);
          }
      }
}
_

これで、overwrite = trueを使用してFSDataOutputStreamfs.create(file, true))を作成しています。

5
harel

Hadoopは、ジョブへの複数の入力パスを許可することで、達成しようとしている効果をすでにサポートしています。ファイルを追加するファイルの単一のディレクトリを作成する代わりに、新しいディレクトリを追加するディレクトリのディレクトリを作成します。集計結果を入力として使用するには、入力グロブをサブディレクトリのワイルドカードとして指定するだけです(例:my-aggregate-output/*)。新しいデータをアグリゲートに出力として「追加」するには、単にアグリゲートの新しい一意のサブディレクトリを出力ディレクトリとして指定します。通常、タイムスタンプまたは入力データから派生したシーケンス番号を使用します(例:my-aggregate-output/20140415154424)。

1
llasram

Hadoopは哲学に従います1回書き込み、何度も読み取ります。したがって、もう一度ディレクトリに書き込もうとすると、新しいディレクトリを作成する必要がある(1回書き込み)と見なされますが、すでに存在しているため、文句を言う。 hadoop fs -rmr /path/to/your/output/から削除できます。データを保持するために、(タイムスタンプまたはハッシュ値に基づいて)動的ディレクトリを作成することをお勧めします。

1
Fabulous

私は同様のユースケースを持っていました、私はこれを解決するためにMultipleOutputsを使用しました。

たとえば、異なるMapReduceジョブが同じディレクトリ/outputDir/に書き込む場合。ジョブ1は/outputDir/job1-part1.txtに書き込み、ジョブ2は/outputDir/job1-part2.txtに書き込みます(既存のファイルを削除せずに)。

メインで、出力ディレクトリをランダムに設定します(新しいジョブを実行する前に削除できます)

FileInputFormat.addInputPath(job, new Path("/randomPath"));

レデューサー/マッパーでMultipleOutputsを使用し、目的のディレクトリに書き込むようにライターを設定します。

public void setup(Context context) {
    MultipleOutputs mos = new MultipleOutputs(context);
}

そして:

mos.write(key, value, "/outputDir/fileOfJobX.txt")

しかし、私のユースケースはそれより少し複雑でした。同じフラットディレクトリに書き込むだけの場合は、別のディレクトリに書き込み、次のようなスクリプトを実行してファイルを移行できます:hadoop fs -mv /tmp/* /outputDir

私の使用例では、各MapReduceジョブは、書き込み中のメッセージの値に基づいて、異なるサブディレクトリに書き込みます。ディレクトリ構造は、次のように多層にすることができます。

/outputDir/
    messageTypeA/
        messageSubTypeA1/
            job1Output/
                job1-part1.txt
                job1-part2.txt
                ...
            job2Output/
                job2-part1.txt
                ...

        messageSubTypeA2/
        ...
    messageTypeB/
    ...

各Mapreduceジョブは、数千のサブディレクトリに書き込むことができます。また、tmpディレクトリに書き込み、各ファイルを正しいディレクトリに移動するコストは高くなります。

0
phanhuy152

私はこの正確な問題に遭遇しました、それはクラスcheckOutputSpecsFileOutputFormatで発生した例外に起因します。私の場合、既存のディレクトリにファイルを追加する多くのジョブを実行したかったので、ファイルに一意の名前が付けられることを保証しました。

checkOutputSpecsメソッドのみをオーバーライドし、ディレクトリがすでに存在するかどうかをチェックする場所でスローされるFileAlreadyExistsExceptionを窒息(無視)する出力フォーマットクラスを作成することで、これを解決しました。

public class OverwriteTextOutputFormat<K, V> extends TextOutputFormat<K, V> {
    @Override
    public void checkOutputSpecs(JobContext job) throws IOException {
        try {
            super.checkOutputSpecs(job);
        }catch (FileAlreadyExistsException ignored){
            // Suffocate the exception
        }
    }
}

そして、ジョブ構成では、LazyOutputFormatMultipleOutputsも使用しました。

LazyOutputFormat.setOutputFormatClass(job, OverwriteTextOutputFormat.class);
0
jdex

ローカルファイルシステムから入力ファイルを(たとえば、追加されたエントリとともに)ロードして、Hadoop分散ファイルシステムにロードしている場合:

hdfs dfs -put  /mylocalfile /user/cloudera/purchase

次に、既存の出力ディレクトリを-fで上書き/再利用することもできます。フォルダを削除または再作成する必要はありません

hdfs dfs -put -f  /updated_mylocalfile /user/cloudera/purchase
0
Laenka-Oss

時間ごとに実行ごとに出力サブディレクトリを作成できます。たとえば、ユーザーからの出力ディレクトリを想定しているとすると、次のように設定します。

FileOutputFormat.setOutputPath(job, new Path(args[1]);

これを次の行で変更します。

String timeStamp = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss", Locale.US).format(new Timestamp(System.currentTimeMillis()));
FileOutputFormat.setOutputPath(job, new Path(args[1] + "/" + timeStamp));
0
Tahsin Turkoz