我想在每天运行Hadoop作业时覆盖/重用现有的输出目录。 实际上,输出目录将存储每天作业运行结果的汇总输出。 如果我指定相同的输出目录,则会给出错误“输出目录已存在”。
如何绕过此验证?
答案 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创建FSDataOutputStream
(fs.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)
我遇到了这个确切的问题,它源于checkOutputSpecs
类FileOutputFormat
中引发的异常。就我而言,我想做很多工作,将文件添加到已经存在的目录中,并保证文件具有唯一的名称。
我通过创建一个输出格式类解决了这一问题,该类仅覆盖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);