如何再次覆盖/重用Hadoop作业的现有输出路径和agian

时间:2011-10-10 13:15:39

标签: hadoop rewrite fileoutputstream

我想在每天运行Hadoop作业时覆盖/重用现有的输出目录。 实际上,输出目录将存储每天作业运行结果的汇总输出。 如果我指定相同的输出目录,则会给出错误“输出目录已存在”。

如何绕过此验证?

10 个答案:

答案 0 :(得分:14)

在运行作业之前删除目录怎么样?

您可以通过shell执行此操作:

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);

答案 1 :(得分:11)

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

答案 2 :(得分:5)

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))。

答案 3 :(得分:1)

Hadoop已经支持您通过允许多个输入路径到达作业而尝试实现的效果。不要试图让你添加更多文件的文件的单个目录,而是要有一个目录目录,你可以在其中添加新目录。要将聚合结果用作输入,只需将输入glob指定为子目录上的通配符(例如,my-aggregate-output/*)。 To&#34;追加&#34;将新数据作为输出添加到聚合中,只需将聚合的新唯一子目录指定为输出目录,通常使用时间戳或从输入数据派生的某个序列号(例如my-aggregate-output/20140415154424)。

答案 4 :(得分:0)

您可以按时间为每次执行创建一个输出子目录。例如,假设您希望用户输出目录,然后按如下所示进行设置:

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));

答案 5 :(得分:0)

我有一个类似的用例,我使用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目录并将每个文件移动到正确目录的成本很高。

答案 6 :(得分:0)

如果正在从本地文件系统将输入文件(例如带有附加条目)加载到hadoop分布式文件系统中,例如:

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

然后,也可以使用-f覆盖/重用现有的输出目录。无需删除或重新创建文件夹

hdfs dfs -put -f  /updated_mylocalfile /user/cloudera/purchase

答案 7 :(得分:0)

Hadoop遵循一次写入,多次读取的哲学。因此,当您尝试再次写入该目录时,它假定必须创建一个新目录(一次写入),但该目录已经存在,所以它抱怨。您可以通过hadoop fs -rmr /path/to/your/output/将其删除。最好创建一个动态目录(例如,基于时间戳或哈希值)以保留数据。

答案 8 :(得分:0)

我遇到了这个确切的问题,它源于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
        }
    }
}

在作业配置中,我使用了LazyOutputFormat以及MultipleOutputs

LazyOutputFormat.setOutputFormatClass(job, OverwriteTextOutputFormat.class);

答案 9 :(得分:0)

您需要在主类中添加设置:

//Configuring the output path from the filesystem into the job
FileOutputFormat.setOutputPath(job, new Path(args[1]));
//auto_delete output dir
OutputPath.getFileSystem(conf).delete(OutputPath);