Concatenamento di più lavori MapReduce in Hadoop


124

In molte situazioni di vita reale in cui si applica MapReduce, gli algoritmi finali finiscono per essere diversi passaggi di MapReduce.

cioè Map1, Reduce1, Map2, Reduce2 e così via.

Quindi hai l'output dell'ultima riduzione necessaria come input per la mappa successiva.

I dati intermedi sono qualcosa che (in generale) non si desidera conservare una volta completata correttamente la pipeline. Anche perché questi dati intermedi sono in generale una struttura di dati (come una "mappa" o un "insieme"), non si vuole fare troppi sforzi per scrivere e leggere queste coppie chiave-valore.

Qual è il modo raccomandato per farlo in Hadoop?

C'è un esempio (semplice) che mostra come gestire questi dati intermedi in modo corretto, inclusa la pulizia in seguito?


2
usando quale framework mapreduce?
Skaffman,

1
Ho modificato la domanda per chiarire che sto parlando di Hadoop.
Niels Basjes,

Consiglierei la gemma suina per questo: github.com/Ganglion/swineherd best, Tobias
Tobias

Risposte:


57

Penso che questo tutorial sulla rete di sviluppatori di Yahoo ti aiuterà in questo: Concatenare i lavori

Si utilizza il JobClient.runJob(). Il percorso di output dei dati dal primo lavoro diventa il percorso di input al secondo lavoro. Questi devono essere passati come argomenti ai tuoi lavori con il codice appropriato per analizzarli e impostare i parametri per il lavoro.

Penso che il metodo sopra potrebbe essere comunque il modo in cui l'API mappata ora più vecchia lo ha fatto, ma dovrebbe comunque funzionare. Ci sarà un metodo simile nella nuova API mapreduce ma non sono sicuro di cosa si tratti.

Per quanto riguarda la rimozione di dati intermedi al termine di un lavoro, è possibile farlo nel codice. Il modo in cui l'ho fatto prima è usare qualcosa come:

FileSystem.delete(Path f, boolean recursive);

Dove il percorso è la posizione su HDFS dei dati. È necessario assicurarsi di eliminare questi dati solo quando nessun altro lavoro lo richiede.


3
Grazie per il link al tutorial di Yahoo. The Chaining Jobs è davvero quello che vuoi se i due sono nella stessa corsa. Quello che stavo cercando è quale sia il modo più semplice se vuoi essere in grado di eseguirli separatamente. Nel tutorial citato ho trovato SequenceFileOutputFormat "Scrive file binari adatti per la lettura nei successivi lavori MapReduce" e il corrispondente SequenceFileInputFormat che rende tutto molto facile da fare. Grazie.
Niels Basjes,

20

Ci sono molti modi per farlo.

(1) Lavori in cascata

Creare l'oggetto JobConf "job1" per il primo lavoro e impostare tutti i parametri con "input" come inputdirectory e "temp" come directory di output. Eseguire questo lavoro:

JobClient.run(job1).

Immediatamente sotto di esso, creare l'oggetto JobConf "job2" per il secondo lavoro e impostare tutti i parametri con "temp" come directory di input e "output" come directory di output. Eseguire questo lavoro:

JobClient.run(job2).

(2) Creare due oggetti JobConf e impostare tutti i parametri in essi proprio come (1) tranne per il fatto che non si utilizza JobClient.run.

Quindi creare due oggetti Job con jobconfs come parametri:

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

Utilizzando l'oggetto jobControl, si specificano le dipendenze del lavoro e quindi si eseguono i lavori:

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

(3) Se hai bisogno di una struttura un po 'come Map + | Ridurre | Mappa *, puoi utilizzare le classi ChainMapper e ChainReducer fornite con Hadoop versione 0.19 e successive.


7

Esistono in realtà diversi modi per farlo. Mi concentrerò su due.

Uno è tramite Riffle ( http://github.com/cwensel/riffle ) una libreria di annotazioni per identificare le cose dipendenti e "eseguirle" in ordine di dipendenza (topologica).

Oppure puoi usare un Cascade (e MapReduceFlow) in Cascading ( http://www.cascading.org/ ). Una versione futura supporterà le annotazioni Riffle, ma ora funziona benissimo con lavori MR JobConf non elaborati.

Una variante su questo è non gestire affatto i lavori MR, ma sviluppare l'applicazione usando l'API a cascata. Quindi JobConf e il concatenamento dei lavori vengono gestiti internamente tramite il planner Cascading e le classi Flow.

In questo modo dedichi il tuo tempo a concentrarti sul tuo problema, non sulla meccanica di gestione dei lavori di Hadoop ecc. Puoi persino sovrapporre diverse lingue (come clojure o jruby) per semplificare ulteriormente lo sviluppo e le applicazioni. http://www.cascading.org/modules.html


6

Ho fatto il concatenamento di lavori usando gli oggetti JobConf uno dopo l'altro. Ho preso l'esempio di WordCount per concatenare i lavori. Un lavoro determina quante volte una parola viene ripetuta nell'output specificato. Il secondo lavoro prende il primo output del lavoro come input e calcola le parole totali nell'input dato. Di seguito è riportato il codice che deve essere inserito nella classe 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);

Il comando per eseguire questi lavori è:

contenitore bin / hadoop TotalWords.

Dobbiamo dare il nome del lavoro finale per il comando. Nel caso sopra, è TotalWords.


5

È possibile eseguire la catena MR nel modo indicato nel codice.

NOTA : è stato fornito solo il codice del driver

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

}

LA MAPPA DI SEQUENCE IS

( JOB1 )-> REDUCE-> ( JOB2 ) MAP
Questo è stato fatto per ottenere le chiavi ordinate, ma ci sono altri modi come l'uso di una mappa ad albero.
Tuttavia, voglio focalizzare la tua attenzione sul modo in cui i lavori sono stati incatenati! !
Grazie




3

Possiamo utilizzare il waitForCompletion(true)metodo del lavoro per definire la dipendenza tra il lavoro.

Nel mio scenario avevo 3 lavori che dipendevano l'uno dall'altro. Nella classe del driver ho usato il codice seguente e funziona come previsto.

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

La tua risposta è su come partecipare a questi lavori in termini di esecuzione. La domanda originale riguardava le migliori strutture di dati. Quindi la tua risposta non è rilevante per questa domanda specifica.
Niels Basjes,

2

La nuova classe org.apache.hadoop.mapreduce.lib.chain.ChainMapper aiuta questo scenario


1
La risposta è buona, ma dovresti aggiungere qualche dettaglio in più su ciò che fa o almeno un link al riferimento API in modo che le persone possano votare in alto
Jeremy Hajek

ChainMapper e ChainReducer sono utilizzati per avere 1 o più mapper prima del Riduci e 0 o più mappatori dopo il Riduci, specifica. (Mapper +) Riduci (Mapper *). Correggimi se sbaglio, ovviamente, ma non credo che questo approccio comporti in serie i lavori come richiesto dall'OP.
oczkoisse,

1

Sebbene esistano complessi motori di flusso di lavoro Hadoop basati su server, ad esempio oozie, ho una semplice libreria Java che consente l'esecuzione di più lavori Hadoop come flusso di lavoro. La configurazione del lavoro e il flusso di lavoro che definiscono la dipendenza tra lavori sono configurati in un file JSON. Tutto è configurabile esternamente e non richiede alcun cambiamento nella mappa esistente per ridurre l'implementazione come parte di un flusso di lavoro.

I dettagli sono disponibili qui. Il codice sorgente e il jar sono disponibili in github.

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

Pranab


1

Penso che oozie aiuti i lavori conseguenti a ricevere gli input direttamente dal lavoro precedente. Ciò evita l'operazione di I / o eseguita con jobcontrol.


1

Se vuoi incatenare a livello di programmazione i tuoi lavori, vorrai utilizzare JobControl. L'utilizzo è abbastanza semplice:

JobControl jobControl = new JobControl(name);

Successivamente aggiungi le istanze ControlledJob. ControlledJob definisce un lavoro con le sue dipendenze, collegando automaticamente ingressi e uscite per adattarsi a una "catena" di lavori.

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

    jobControl.run();

inizia la catena. Ti consigliamo di inserirlo in un thread specifico. Ciò consente di verificare lo stato della catena durante l'esecuzione:

    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

Come indicato nel requisito in base al quale si desidera che o / p di MRJob1 sia l'i / p di MRJob2 e così via, è possibile prendere in considerazione l'utilizzo del flusso di lavoro oozie per questo caso d'uso. Inoltre potresti prendere in considerazione la possibilità di scrivere i tuoi dati intermedi su HDFS poiché verranno utilizzati dal prossimo MRJob. E al termine del processo è possibile ripulire i dati intermedi.

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.