在Hadoop中链接多个MapReduce作业


124

在许多应用MapReduce的现实情况中,最终的算法最终是几个MapReduce步骤。

即Map1,Reduce1,Map2,Reduce2等。

因此,您具有来自上一个reduce的输出,该输出将作为下一个映射的输入。

一旦管道成功完成,您通常就不想保留中间数据。另外,由于此中间数据​​通常是某种数据结构(例如“映射”或“集合”),因此您不想在写入和读取这些键值对时花费太多精力。

在Hadoop中推荐这样做的推荐方式是什么?

是否有一个(简单的)示例显示如何以正确的方式(包括随后的清理)处理此中间数据​​?


2
使用哪个mapreduce框架?
skaffman 2010年

1
我编辑了问题以澄清我在谈论Hadoop。
Niels Basjes,2010年

我建议为此使用swineherd宝石:最好是github.com/Ganglion/swineherd,Tobias
Tobias

Answers:


57

我认为Yahoo开发人员网络上的本教程将为您提供帮助:Chaining Jobs

您使用JobClient.runJob()。第一个作业的数据输出路径成为第二个作业的输入路径。需要使用适当的代码将这些作为参数传递给您的作业,以解析它们并为作业设置参数。

我认为上述方法可能是现在较旧的mapred API所采用的方法,但仍然可以使用。新的mapreduce API中将有一个类似的方法,但我不确定它是什么。

至于在作业完成后删除中间数据,您可以在代码中执行。我以前做过的方式是使用类似:

FileSystem.delete(Path f, boolean recursive);

路径是数据在HDFS上的位置。您需要确保仅在没有其他作业需要它时才删除该数据。


3
感谢您链接到Yahoo教程。如果两者同时运行,确实是您想要的。我要寻找的是如果您希望能够单独运行它们,那么简单的方法是做什么。在提到的教程中,我发现SequenceFileOutputFormat“编写适合于读入后续MapReduce作业的二进制文件”和匹配的SequenceFileInputFormat,这使它们都非常容易实现。谢谢。
Niels Basjes,2010年

20

有很多方法可以做到。

(1)级联工作

为第一个作业创建JobConf对象“ job1”,并将所有参数设置为“ input”作为输入目录,“ temp”作为输出目录。执行此作业:

JobClient.run(job1).

在其正下方,为第二个作业创建JobConf对象“ job2”,并将所有参数设置为“ temp”作为输入目录,将“ output”作为输出目录。执行此作业:

JobClient.run(job2).

(2)创建两个JobConf对象,并像(1)一样在其中设置所有参数,除了不使用JobClient.run。

然后创建两个以jobconfs为参数的Job对象:

Job job1=new Job(jobconf1); 
Job job2=new Job(jobconf2);

使用jobControl对象,您可以指定作业依赖性,然后运行作业:

JobControl jbcntrl=new JobControl("jbcntrl");
jbcntrl.addJob(job1);
jbcntrl.addJob(job2);
job2.addDependingJob(job1);
jbcntrl.run();

(3)如果需要类似于Map + |的结构。减少| Map *,您可以使用Hadoop版本0.19及更高版本随附的ChainMapper和ChainReducer类。


7

实际上,有很多方法可以做到这一点。我将专注于两个。

一种是通过Riffle(http://github.com/cwensel/riffle)的注释库,用于识别依赖的事物并以依赖(拓扑)顺序“执行”它们。

或者,您可以在Cascading(http://www.cascading.org/)中使用Cascade(和MapReduceFlow )。将来的版本将支持Riffle注释,但现在可以与原始MR JobConf作业一起使用。

一个变种是根本不手工管理MR作业,而是使用Cascading API开发应用程序。然后,通过级联计划程序和Flow类在内部处理JobConf和作业链。

这样,您就可以将时间花在解决问题上,而不是在管理Hadoop作业等机制上。您甚至可以在顶部放置不同的语言(例如clojure或jruby),以进一步简化开发和应用程序。http://www.cascading.org/modules.html


6

我已经与JobConf对象一起完成了工作链接。我以WordCount为例来链接工作。一项工作可以计算出给定输出中单词重复的次数。第二个作业将第一个作业的输出作为输入,并找出给定输入中的总单词数。下面是需要放在Driver类中的代码。

    //First Job - Counts, how many times a word encountered in a given file 
    JobConf job1 = new JobConf(WordCount.class);
    job1.setJobName("WordCount");

    job1.setOutputKeyClass(Text.class);
    job1.setOutputValueClass(IntWritable.class);

    job1.setMapperClass(WordCountMapper.class);
    job1.setCombinerClass(WordCountReducer.class);
    job1.setReducerClass(WordCountReducer.class);

    job1.setInputFormat(TextInputFormat.class);
    job1.setOutputFormat(TextOutputFormat.class);

    //Ensure that a folder with the "input_data" exists on HDFS and contains the input files
    FileInputFormat.setInputPaths(job1, new Path("input_data"));

    //"first_job_output" contains data that how many times a word occurred in the given file
    //This will be the input to the second job. For second job, input data name should be
    //"first_job_output". 
    FileOutputFormat.setOutputPath(job1, new Path("first_job_output"));

    JobClient.runJob(job1);


    //Second Job - Counts total number of words in a given file

    JobConf job2 = new JobConf(TotalWords.class);
    job2.setJobName("TotalWords");

    job2.setOutputKeyClass(Text.class);
    job2.setOutputValueClass(IntWritable.class);

    job2.setMapperClass(TotalWordsMapper.class);
    job2.setCombinerClass(TotalWordsReducer.class);
    job2.setReducerClass(TotalWordsReducer.class);

    job2.setInputFormat(TextInputFormat.class);
    job2.setOutputFormat(TextOutputFormat.class);

    //Path name for this job should match first job's output path name
    FileInputFormat.setInputPaths(job2, new Path("first_job_output"));

    //This will contain the final output. If you want to send this jobs output
    //as input to third job, then third jobs input path name should be "second_job_output"
    //In this way, jobs can be chained, sending output one to other as input and get the
    //final output
    FileOutputFormat.setOutputPath(job2, new Path("second_job_output"));

    JobClient.runJob(job2);

运行这些作业的命令是:

bin / hadoop jar TotalWords。

我们需要为命令指定最终作业名称。在上述情况下,它是TotalWords。


5

您可以按照代码中给出的方式运行MR链。

请注意:仅提供了驱动程序代码

public class WordCountSorting {
// here the word keys shall be sorted
      //let us write the wordcount logic first

      public static void main(String[] args)throws IOException,InterruptedException,ClassNotFoundException {
            //THE DRIVER CODE FOR MR CHAIN
            Configuration conf1=new Configuration();
            Job j1=Job.getInstance(conf1);
            j1.setJarByClass(WordCountSorting.class);
            j1.setMapperClass(MyMapper.class);
            j1.setReducerClass(MyReducer.class);

            j1.setMapOutputKeyClass(Text.class);
            j1.setMapOutputValueClass(IntWritable.class);
            j1.setOutputKeyClass(LongWritable.class);
            j1.setOutputValueClass(Text.class);
            Path outputPath=new Path("FirstMapper");
            FileInputFormat.addInputPath(j1,new Path(args[0]));
                  FileOutputFormat.setOutputPath(j1,outputPath);
                  outputPath.getFileSystem(conf1).delete(outputPath);
            j1.waitForCompletion(true);
                  Configuration conf2=new Configuration();
                  Job j2=Job.getInstance(conf2);
                  j2.setJarByClass(WordCountSorting.class);
                  j2.setMapperClass(MyMapper2.class);
                  j2.setNumReduceTasks(0);
                  j2.setOutputKeyClass(Text.class);
                  j2.setOutputValueClass(IntWritable.class);
                  Path outputPath1=new Path(args[1]);
                  FileInputFormat.addInputPath(j2, outputPath);
                  FileOutputFormat.setOutputPath(j2, outputPath1);
                  outputPath1.getFileSystem(conf2).delete(outputPath1, true);
                  System.exit(j2.waitForCompletion(true)?0:1);
      }

}

顺序是

JOB1)MAP-> REDUCE->(JOB2)MAP
这样做是为了对键进行排序,但是还有更多的方法,例如使用树图
。 !
谢谢




3

我们可以利用waitForCompletion(true)Job 的方法来定义Job之间的依赖关系。

在我的场景中,我有3个工作相互依赖。在驱动程序类中,我使用了以下代码,它可以按预期工作。

public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub

        CCJobExecution ccJobExecution = new CCJobExecution();

        Job distanceTimeFraudJob = ccJobExecution.configureDistanceTimeFraud(new Configuration(),args[0], args[1]);
        Job spendingFraudJob = ccJobExecution.configureSpendingFraud(new Configuration(),args[0], args[1]);
        Job locationFraudJob = ccJobExecution.configureLocationFraud(new Configuration(),args[0], args[1]);

        System.out.println("****************Started Executing distanceTimeFraudJob ================");
        distanceTimeFraudJob.submit();
        if(distanceTimeFraudJob.waitForCompletion(true))
        {
            System.out.println("=================Completed DistanceTimeFraudJob================= ");
            System.out.println("=================Started Executing spendingFraudJob ================");
            spendingFraudJob.submit();
            if(spendingFraudJob.waitForCompletion(true))
            {
                System.out.println("=================Completed spendingFraudJob================= ");
                System.out.println("=================Started locationFraudJob================= ");
                locationFraudJob.submit();
                if(locationFraudJob.waitForCompletion(true))
                {
                    System.out.println("=================Completed locationFraudJob=================");
                }
            }
        }
    }

您的答案是关于如何在执行方面加入这些工作。最初的问题是关于最佳数据结构的。因此,您的答案与该特定问题无关。
Niels Basjes

2

新的类org.apache.hadoop.mapreduce.lib.chain.ChainMapper帮助解决此问题


1
答案是好的-但您应该添加有关其功能的更多详细信息,或者至少添加指向API参考的链接,以便人们可以投票
Jeremy Hajek,2016年

ChainMapper和ChainReducer用于在Reduce规范之前具有1个或多个映射器,在Reduce规范之后具有0个或多个映射器。(映射器+)减少(映射器*)。如果我明显错了,请纠正我,但我认为这种方法无法按照OP的要求完成一系列的工作。
oczkoisse

1

尽管存在基于复杂服务器的Hadoop工作流引擎(例如oozie),但我有一个简单的Java库,该库支持将多个Hadoop作业作为工作流执行。定义作业间依赖性的作业配置和工作流在JSON文件中配置。一切都可以在外部进行配置,并且无需对现有地图进行任何更改即可将实施简化成为工作流程的一部分。

详细信息可以在这里找到。源代码和jar在github中可用。

http://pkghosh.wordpress.com/2011/05/22/hadoop-orchestration/

普拉纳布


1

我认为oozie帮助后续工作直接从上一个工作中接收输入。这避免了通过作业控制执行的I / O操作。


1

如果要以编程方式链接您的作业,则需要使用JobControl。用法很简单:

JobControl jobControl = new JobControl(name);

之后,添加ControlledJob实例。ControlledJob使用其依赖项定义作业,从而自动插入输入和输出以适合作业的“链”。

    jobControl.add(new ControlledJob(job, Arrays.asList(controlledjob1, controlledjob2));

    jobControl.run();

启动链。您将需要将其放入一个适当的线程中。这样可以检查链条运行时的状态:

    while (!jobControl.allFinished()) {
        System.out.println("Jobs in waiting state: " + jobControl.getWaitingJobList().size());
        System.out.println("Jobs in ready state: " + jobControl.getReadyJobsList().size());
        System.out.println("Jobs in running state: " + jobControl.getRunningJobList().size());
        List<ControlledJob> successfulJobList = jobControl.getSuccessfulJobList();
        System.out.println("Jobs in success state: " + successfulJobList.size());
        List<ControlledJob> failedJobList = jobControl.getFailedJobList();
        System.out.println("Jobs in failed state: " + failedJobList.size());
    }

0

正如您在要求中提到的要让MRJob1的o / p成为MRJob2的i / p等,您可以考虑在此用例中使用oozie工作流程。您也可以考虑将中间数据写入HDFS,因为它将由下一个MRJob使用。作业完成后,您可以清理中间数据。

<start to="mr-action1"/>
<action name="mr-action1">
   <!-- action for MRJob1-->
   <!-- set output path = /tmp/intermediate/mr1-->
    <ok to="end"/>
    <error to="end"/>
</action>

<action name="mr-action2">
   <!-- action for MRJob2-->
   <!-- set input path = /tmp/intermediate/mr1-->
    <ok to="end"/>
    <error to="end"/>
</action>

<action name="success">
        <!-- action for success-->
    <ok to="end"/>
    <error to="end"/>
</action>

<action name="fail">
        <!-- action for fail-->
    <ok to="end"/>
    <error to="end"/>
</action>

<end name="end"/>

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.