Verketten mehrerer MapReduce-Jobs in Hadoop

124

In vielen realen Situationen, in denen Sie MapReduce anwenden, bestehen die endgültigen Algorithmen aus mehreren MapReduce-Schritten.

dh Map1, Reduce1, Map2, Reduce2 und so weiter.

Sie haben also die Ausgabe der letzten Reduzierung, die als Eingabe für die nächste Karte benötigt wird.

Die Zwischendaten möchten Sie (im Allgemeinen) nicht behalten, sobald die Pipeline erfolgreich abgeschlossen wurde. Auch weil diese Zwischendaten im Allgemeinen eine Datenstruktur haben (wie eine 'Karte' oder eine 'Menge'), möchten Sie nicht zu viel Aufwand beim Schreiben und Lesen dieser Schlüssel-Wert-Paare betreiben.

Was ist die empfohlene Vorgehensweise in Hadoop?

Gibt es ein (einfaches) Beispiel, das zeigt, wie mit diesen Zwischendaten richtig umgegangen wird, einschließlich der anschließenden Bereinigung?

Niels Basjes
quelle
2
Mit welchem ​​Mapreduce-Framework?
Skaffman
1
Ich habe die Frage bearbeitet, um zu verdeutlichen, dass es sich um Hadoop handelt.
Niels Basjes
Ich würde das Schweinehirt Juwel dafür empfehlen: github.com/Ganglion/swineherd am besten, Tobias
Tobias

Antworten:

57

Ich denke, dieses Tutorial im Entwicklernetzwerk von Yahoo wird Ihnen dabei helfen: Verketten von Jobs

Sie verwenden die JobClient.runJob(). Der Ausgabepfad der Daten aus dem ersten Job wird zum Eingabepfad zu Ihrem zweiten Job. Diese müssen als Argumente mit entsprechendem Code an Ihre Jobs übergeben werden, um sie zu analysieren und die Parameter für den Job einzurichten.

Ich denke, dass die obige Methode möglicherweise so ist, wie es die jetzt ältere Mapred-API getan hat, aber sie sollte trotzdem funktionieren. Es wird eine ähnliche Methode in der neuen Mapreduce-API geben, aber ich bin mir nicht sicher, was es ist.

Wenn Sie nach Abschluss eines Auftrags Zwischendaten entfernen, können Sie dies in Ihrem Code tun. Ich habe es schon einmal so gemacht:

FileSystem.delete(Path f, boolean recursive);

Dabei ist der Pfad der Speicherort der Daten auf HDFS. Sie müssen sicherstellen, dass Sie diese Daten nur löschen, wenn kein anderer Job dies erfordert.

Binärer Nerd
quelle
3
Vielen Dank für den Link zum Yahoo-Tutorial. Die Verkettungsjobs sind in der Tat das, was Sie wollen, wenn sich beide im selben Lauf befinden. Was ich gesucht habe, ist, was der einfache Weg ist, wenn Sie sie separat ausführen möchten. In dem erwähnten Tutorial fand ich SequenceFileOutputFormat "Schreibt Binärdateien, die zum Einlesen in nachfolgende MapReduce-Jobs geeignet sind" und das passende SequenceFileInputFormat, was alles sehr einfach macht. Vielen Dank.
Niels Basjes
20

Es gibt viele Möglichkeiten, wie Sie dies tun können.

(1) Kaskadierende Jobs

Erstellen Sie das JobConf-Objekt "job1" für den ersten Job und legen Sie alle Parameter mit "input" als Eingabeverzeichnis und "temp" als Ausgabeverzeichnis fest. Führen Sie diesen Job aus:

JobClient.run(job1).

Erstellen Sie unmittelbar darunter das JobConf-Objekt "job2" für den zweiten Job und legen Sie alle Parameter mit "temp" als Eingabeverzeichnis und "output" als Ausgabeverzeichnis fest. Führen Sie diesen Job aus:

JobClient.run(job2).

(2) Erstellen Sie zwei JobConf-Objekte und legen Sie alle darin enthaltenen Parameter wie in (1) fest, außer dass Sie JobClient.run nicht verwenden.

Erstellen Sie dann zwei Jobobjekte mit jobconfs als Parametern:

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

Mit dem jobControl-Objekt geben Sie die Jobabhängigkeiten an und führen dann die Jobs aus:

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

(3) Wenn Sie eine Struktur wie Map + | benötigen Reduzieren | Map * können Sie die Klassen ChainMapper und ChainReducer verwenden, die mit Hadoop Version 0.19 und höher geliefert werden.

user381928
quelle
7

Es gibt tatsächlich eine Reihe von Möglichkeiten, dies zu tun. Ich werde mich auf zwei konzentrieren.

Eine davon ist über Riffle ( http://github.com/cwensel/riffle ) eine Anmerkungsbibliothek, mit der abhängige Dinge identifiziert und in abhängiger (topologischer) Reihenfolge "ausgeführt" werden können.

Oder Sie können eine Kaskade (und MapReduceFlow) in Cascading ( http://www.cascading.org/ ) verwenden. Eine zukünftige Version wird Riffle-Annotationen unterstützen, funktioniert aber jetzt hervorragend mit unformatierten MR JobConf-Jobs.

Eine Variante besteht darin, MR-Jobs überhaupt nicht manuell zu verwalten, sondern Ihre Anwendung mithilfe der Cascading-API zu entwickeln. Anschließend werden die JobConf und die Jobverkettung intern über die Klassen Cascading Planer und Flow abgewickelt.

Auf diese Weise konzentrieren Sie sich auf Ihr Problem und nicht auf die Mechanismen zur Verwaltung von Hadoop-Jobs usw. Sie können sogar verschiedene Sprachen (wie Clojure oder Jruby) überlagern, um Ihre Entwicklung und Anwendungen noch weiter zu vereinfachen. http://www.cascading.org/modules.html

cwensel
quelle
6

Ich habe die Jobverkettung nacheinander mit JobConf-Objekten durchgeführt. Ich habe ein WordCount-Beispiel für die Verkettung der Jobs verwendet. Ein Job ermittelt, wie oft ein Wort in der angegebenen Ausgabe wiederholt wird. Der zweite Job verwendet die erste Jobausgabe als Eingabe und ermittelt die Gesamtzahl der Wörter in der angegebenen Eingabe. Unten finden Sie den Code, der in die Treiberklasse eingefügt werden muss.

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

Der Befehl zum Ausführen dieser Jobs lautet:

bin / hadoop jar TotalWords.

Wir müssen den endgültigen Jobnamen für den Befehl angeben. Im obigen Fall handelt es sich um TotalWords.

psrklr
quelle
5

Sie können die MR-Kette wie im Code angegeben ausführen.

BITTE BEACHTEN SIE : Es wurde nur der Treibercode angegeben

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

}

DIE SEQUENZ IST

( JOB1 ) KARTE-> REDUZIEREN-> ( JOB2 ) KARTE
Dies wurde durchgeführt, um die Schlüssel zu sortieren. Es gibt jedoch noch weitere Möglichkeiten, z. B. die Verwendung einer Baumkarte.
Ich möchte Ihre Aufmerksamkeit jedoch auf die Art und Weise richten, wie die Jobs verkettet wurden! !
Danke dir

Aniruddha Sinha
quelle
3

Wir können die waitForCompletion(true)Methode des Jobs verwenden, um die Abhängigkeit zwischen dem Job zu definieren.

In meinem Szenario hatte ich 3 Jobs, die voneinander abhängig waren. In der Treiberklasse habe ich den folgenden Code verwendet und er funktioniert wie erwartet.

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=================");
                }
            }
        }
    }
Shivaprasad
quelle
In Ihrer Antwort geht es darum, wie Sie diese Jobs in Bezug auf die Ausführung verbinden können. Die ursprüngliche Frage betraf die besten Datenstrukturen. Ihre Antwort ist also für diese spezielle Frage nicht relevant.
Niels Basjes
2

Die neue Klasse org.apache.hadoop.mapreduce.lib.chain.ChainMapper hilft diesem Szenario

Xavi
quelle
1
Die Antwort ist gut - aber Sie sollten etwas mehr Details darüber hinzufügen, was es tut, oder zumindest einen Link zur API-Referenz, damit die Leute abstimmen können
Jeremy Hajek
ChainMapper und ChainReducer werden verwendet, um 1 oder mehr Mapper vor dem Reduzieren und 0 oder mehr Mapper nach dem Reduzieren zu haben. (Mapper +) Reduzieren (Mapper *). Korrigieren Sie mich, wenn ich offensichtlich falsch liege, aber ich glaube nicht, dass dieser Ansatz eine serielle Verkettung der Jobs bewirkt, wie OP es verlangt hat.
oczkoisse
1

Obwohl es komplexe serverbasierte Hadoop-Workflow-Engines gibt, z. B. oozie, habe ich eine einfache Java-Bibliothek, die die Ausführung mehrerer Hadoop-Jobs als Workflow ermöglicht. Die Jobkonfiguration und der Workflow, die die Abhängigkeit zwischen Jobs definieren, werden in einer JSON-Datei konfiguriert. Alles ist extern konfigurierbar und erfordert keine Änderung der vorhandenen Map Reduce-Implementierung, um Teil eines Workflows zu sein.

Details finden Sie hier. Quellcode und JAR sind in Github verfügbar.

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

Pranab

Pranab
quelle
1

Ich denke, oozie hilft den nachfolgenden Jobs, die Eingaben direkt vom vorherigen Job zu erhalten. Dies vermeidet die mit Jobcontrol ausgeführte E / A-Operation.

stholy
quelle
1

Wenn Sie Ihre Jobs programmgesteuert verketten möchten, müssen Sie JobControl verwenden. Die Verwendung ist recht einfach:

JobControl jobControl = new JobControl(name);

Danach fügen Sie ControlledJob-Instanzen hinzu. ControlledJob definiert einen Job mit seinen Abhängigkeiten und fügt so automatisch Ein- und Ausgänge ein, um sie an eine "Kette" von Jobs anzupassen.

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

    jobControl.run();

startet die Kette. Sie werden das in einen speziellen Thread einfügen wollen. Auf diese Weise können Sie den Status Ihrer Kette überprüfen, während sie ausgeführt wird:

    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());
    }
Erik Schmiegelow
quelle
0

Wie Sie in Ihrer Anforderung erwähnt haben, dass o / p von MRJob1 das i / p von MRJob2 usw. sein soll, können Sie in Betracht ziehen, den oozie-Workflow für diesen Anwendungsfall zu verwenden. Sie können auch Ihre Zwischendaten in HDFS schreiben, da diese vom nächsten MRJob verwendet werden. Und nachdem der Auftrag abgeschlossen ist, können Sie Ihre Zwischendaten bereinigen.

<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"/>

Neha Kumari
quelle