Hadoop MapReduce Beispiel: Erste Java Programm mit Code

⚡ Intelligente Zusammenfassung

Hadoop MapReduce-Programme werden als drei geschrieben Java Klassen, ein Mapper, ein Reducer und ein Treiber werden kompiliert, in ein JAR-Archiv verpackt und an den Cluster übermittelt, um die Verkäufe pro Land zu zählen.

  • 🔘 Datensatz: Die Datei SalesJan2009.csv enthält eine Transaktion pro Zeile, wobei das Land in der achten, durch Kommas getrennten Spalte angegeben ist.
  • ☑️ Mapper: SalesMapper teilt jede Zeile auf und gibt das Land zusammen mit dem konstanten Wert eins aus.
  • ✅ Reduzierung: SalesCountryReducer summiert die für jedes Land eingehenden Bestellungen zu einer einzigen Häufigkeitszählung.
  • 🧪 Treiber: SalesCountryDriver benennt den Job, deklariert Schlüssel- und Werttypen und verbindet beide Klassen miteinander.
  • ️ Build: Mit javac kompilieren, einen Main-Class-Manifesteintrag hinzufügen und anschließend alles mit jar cfm verpacken.
  • ⚠️ Run: Kopieren Sie die CSV-Datei in HDFS, übermitteln Sie die JAR-Datei und lesen Sie Teil-00000 aus dem Ausgabeverzeichnis.

Hadoop MapReduce-Beispiel: Erstellung eines ersten Java Programm

In diesem Tutorial erfahren Sie, wie Sie Hadoop mit MapReduce-Beispielen verwenden. Die verwendeten Eingabedaten sind SalesJan2009.csvSie enthält verkaufsbezogene Informationen wie Produktname, Preis, Zahlungsart, Stadt und Land des Kunden. Ziel ist es, die Anzahl der in jedem Land verkauften Produkte zu ermitteln.

Erstes Hadoop MapReduce-Programm

Jetzt hier MapReduce-Tutorial, erstellen wir unser erstes Java MapReduce-Programm:

Der untenstehende Screenshot zeigt die Rohdaten von SalesJan2009, wobei jede Zeile eine Transaktion darstellt und das Land in der achten, durch Kommas getrennten Spalte angegeben ist.

Die Verkaufsdaten vom Januar 2009 wurden in einer Tabellenkalkulation mit Transaktionsspalten geöffnet.

Stellen Sie sicher, dass Hadoop installiert ist. Bevor Sie mit dem eigentlichen Prozess beginnen, ändern Sie den Benutzer in 'hduser' (die ID, die bei der Konfiguration von Hadoop verwendet wurde – Sie können zur Benutzer-ID wechseln, die Sie während Ihrer eigenen Hadoop-Konfiguration verwendet haben).

su - hduser_

Die Eingabeaufforderung ändert sich zum hduser-Konto, wie unten dargestellt.

Terminal-Eingabeaufforderung nach dem Wechsel zum hduser-Konto

Schritt 1) ​​Projektverzeichnis und Quelldateien erstellen

Erstellen Sie ein neues Verzeichnis mit dem Namen MapReduceTutorial, wie im folgenden MapReduce-Beispiel gezeigt.

sudo mkdir MapReduceTutorial

Der Befehl mkdir erstellt das Verzeichnis MapReduceTutorial.

Berechtigungen erteilen

sudo chmod -R 777 MapReduceTutorial

chmod-Befehl, der MapReduceTutorial volle Berechtigungen gewährt

Erstelle die drei Java Die Quelldateien befinden sich im Ordner „MapReduceTutorial“. Beachten Sie, dass alle drei die ältere Version verwenden. org.apache.hadoop.mapred API, die immer noch mit Hadoop 3.x ausgeliefert wird.

SalesMapper.java

package SalesCountry;

import java.io.IOException;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.*;

public class SalesMapper extends MapReduceBase implements Mapper <LongWritable, Text, Text, IntWritable> {
	private final static IntWritable one = new IntWritable(1);

	public void map(LongWritable key, Text value, OutputCollector <Text, IntWritable> output, Reporter reporter) throws IOException {

		String valueString = value.toString();
		String[] SingleCountryData = valueString.split(",");
		output.collect(new Text(SingleCountryData[7]), one);
	}
}

SalesCountryReducer.java

package SalesCountry;

import java.io.IOException;
import java.util.*;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.*;

public class SalesCountryReducer extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> {

	public void reduce(Text t_key, Iterator<IntWritable> values, OutputCollector<Text,IntWritable> output, Reporter reporter) throws IOException {
		Text key = t_key;
		int frequencyForCountry = 0;
		while (values.hasNext()) {
			// replace type of value with the actual type of our value
			IntWritable value = (IntWritable) values.next();
			frequencyForCountry += value.get();
			
		}
		output.collect(key, new IntWritable(frequencyForCountry));
	}
}

SalesCountryDriver.java

package SalesCountry;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapred.*;

public class SalesCountryDriver {
    public static void main(String[] args) {
        JobClient my_client = new JobClient();
        // Create a configuration object for the job
        JobConf job_conf = new JobConf(SalesCountryDriver.class);

        // Set a name of the Job
        job_conf.setJobName("SalePerCountry");

        // Specify data type of output key and value
        job_conf.setOutputKeyClass(Text.class);
        job_conf.setOutputValueClass(IntWritable.class);

        // Specify names of Mapper and Reducer Class
        job_conf.setMapperClass(SalesCountry.SalesMapper.class);
        job_conf.setReducerClass(SalesCountry.SalesCountryReducer.class);

        // Specify formats of the data type of Input and output
        job_conf.setInputFormat(TextInputFormat.class);
        job_conf.setOutputFormat(TextOutputFormat.class);

        // Set input and output directories using command line arguments, 
        //arg[0] = name of input directory on HDFS, and arg[1] =  name of output directory to be created to store the output file.

        FileInputFormat.setInputPaths(job_conf, new Path(args[0]));
        FileOutputFormat.setOutputPath(job_conf, new Path(args[1]));

        my_client.setConf(job_conf);
        try {
            // Run the job 
            JobClient.runJob(job_conf);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Laden Sie die Dateien hier herunter

Das Archiv entfaltet sich in dieselben drei Quelldateien, wie hier gezeigt.

Extracted-Archiv mit Auflistung von SalesMapper, SalesCountryReducer und SalesCountryDriver

Überprüfen Sie die Dateiberechtigungen aller dieser Dateien

Lange Liste mit Dateiberechtigungen für die drei Java Quelldaten

Falls die Leseberechtigung fehlt, erteilen Sie sie:

chmod-Befehl, der Leseberechtigung hinzufügt Java Quelldaten

Schritt 2) Exportieren Sie den Hadoop-Classpath

Exportieren Sie den Klassenpfad wie im folgenden Hadoop-Beispiel gezeigt.

export CLASSPATH="$HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-client-core-2.2.0.jar:$HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-client-common-2.2.0.jar:$HADOOP_HOME/share/hadoop/common/hadoop-common-2.2.0.jar:~/MapReduceTutorial/SalesCountry/*:$HADOOP_HOME/lib/*"

Der exportierte Klassenpfad wird an der Eingabeaufforderung wieder angezeigt.

Shell-Eingabeaufforderung nach dem Exportieren der Hadoop CLASSPATH-Variablen

Schritt 3) Kompilieren Sie die Java Dateien

Kompilieren Sie die Java Die Dateien (diese Dateien befinden sich im Verzeichnis Final-MapReduceHandsOn) und ihre Klassendateien werden im Paketverzeichnis abgelegt.

javac -d . SalesMapper.java SalesCountryReducer.java SalesCountryDriver.java

javac-Ausgabe, die während der Kompilierung eine Warnung bezüglich veralteter Funktionen anzeigt

Diese Warnung kann getrost ignoriert werden – sie besagt lediglich, dass die mapred-API veraltet ist.

Diese Kompilierung erstellt im aktuellen Verzeichnis ein Verzeichnis mit dem Namen des im Paket angegebenen Pakets. Java Die Quelldatei (in unserem Fall SalesCountry) enthält alle kompilierten Klassendateien.

Das Paketverzeichnis SalesCountry enthält die drei kompilierten Klassendateien.

Schritt 4) Erstellen Sie die Manifestdatei

Erstelle eine neue Datei namens Manifest.txt

sudo gedit Manifest.txt

Fügen Sie folgende Zeile hinzu:

Main-Class: SalesCountry.SalesCountryDriver

gedit Fenster, das den Eintrag „Main-Class“ in Manifest.txt anzeigt

SalesCountry.SalesCountryDriver ist der Name der Hauptklasse. Bitte beachten Sie, dass Sie am Ende dieser Zeile die Eingabetaste drücken müssen.

Schritt 5) Packen Sie die Klassen in ein JAR-Archiv.

Erstellen Sie eine Jar-Datei

jar cfm ProductSalePerCountry.jar Manifest.txt SalesCountry/*.class

Der Befehl `jar` erstellt die Datei `ProductSalePerCountry.jar` aus dem Manifest.

Überprüfen Sie, ob die JAR-Datei erstellt wurde

Verzeichniseintrag, der bestätigt, dass ProductSalePerCountry.jar erstellt wurde

Schritt 6) Hadoop starten

Starten Sie Hadoop

$HADOOP_HOME/sbin/start-dfs.sh
$HADOOP_HOME/sbin/start-yarn.sh

Schritt 7) Kopieren Sie die Eingabedatei in HDFS.

Kopieren Sie die Datei SalesJan2009.csv nach ~/inputMapReduce

Verwenden Sie nun den folgenden Befehl, um ~/inputMapReduce nach HDFS zu kopieren.

$HADOOP_HOME/bin/hdfs dfs -copyFromLocal ~/inputMapReduce /

Ausgabe des Befehls copyFromLocal mit einer Warnung bezüglich einer nativen Bibliothek

Wir können diese Warnung getrost ignorieren.

Überprüfen Sie, ob eine Datei tatsächlich kopiert wird oder nicht.

$HADOOP_HOME/bin/hdfs dfs -ls /inputMapReduce

hdfs dfs -ls listet die Datei SalesJan2009.csv im Verzeichnis inputMapReduce auf

Schritt 8) Führen Sie den MapReduce-Job aus.

Führen Sie den MapReduce-Job aus

$HADOOP_HOME/bin/hadoop jar ProductSalePerCountry.jar /inputMapReduce /mapreduce_output_sales

Konsolenausgabe während der Ausführung des Jobs „SalePerCountry“ auf dem Cluster

Dadurch wird ein Ausgabeverzeichnis mit dem Namen „mapreduce_output_sales“ erstellt HDFS. Der Inhalt dieses Verzeichnisses ist eine Datei mit den Produktverkäufen pro Land.

Schritt 9) Lesen Sie die Ergebnisse.

Das Ergebnis kann über die Befehlsschnittstelle wie folgt angezeigt werden:

$HADOOP_HOME/bin/hdfs dfs -cat /mapreduce_output_sales/part-00000

Vom Befehl hdfs dfs -cat ausgegebene Länder- und Verkaufszahlenpaare.

Die Ergebnisse können auch über eine Weboberfläche eingesehen werden.

Öffne http://localhost:50070/ in einem Webbrowser. Bei Hadoop 3.x wurde die NameNode-Weboberfläche auf Port verschoben. 9870, also benutze http://localhost:9870/ dort statt.

NameNode-Webinterface-Startseite im Browser

Wählen Sie nun „Dateisystem durchsuchen“ und navigieren Sie zu /mapreduce_output_sales

HDFS-Dateibrowser, der das Verzeichnis mapreduce_output_sales anzeigt

Öffnen Sie Teil-r-00000

Die Ergebnisdatei part-r-00000 wurde im HDFS-Browser geöffnet.

Erläuterung der SalesMapper-Klasse

Nachdem der gesamte Prozess von Anfang bis Ende durchlaufen wurde, werden in den nächsten drei Abschnitten die einzelnen Klassen detailliert beschrieben.

In diesem Abschnitt werden wir die Implementierung der SalesMapper-Klasse verstehen.

1. Zunächst legen wir einen Paketnamen für unsere Klasse fest. Dieser lautet „SalesCountry“. Bitte beachten Sie, dass die Ausgabedatei der Kompilierung, „SalesMapper.class“, in einem Verzeichnis mit dem Namen dieses Pakets gespeichert wird: „SalesCountry“.

Anschließend importieren wir Bibliothekspakete.

Der folgende Screenshot zeigt eine Implementierung der SalesMapper-Klasse.

Editoransicht der vollständigen Implementierung der SalesMapper-Klasse

Musteranfrage Code Erläuterung:

1. SalesMapper-Klassendefinition –

public class SalesMapper extends MapReduceBase implements Mapper<LongWritable, Text, Text, IntWritable> {

Jede Mapper-Klasse muss von der MapReduceBase-Klasse abgeleitet werden und das Mapper-Interface implementieren.

2. Definieren der „Karten“-Funktion –

public void map(LongWritable key,
         Text value,
OutputCollector<Text, IntWritable> output,
Reporter reporter) throws IOException

Der Hauptteil der Mapper-Klasse ist eine 'map()'-Methode, die vier Argumente akzeptiert.

Bei jedem Aufruf der Methode 'map()' wird ein Schlüssel-Wert-Paar übergeben (in diesem Code 'key' und 'value').

Die Methode 'map()' beginnt damit, den als Argument übergebenen Eingabetext aufzuteilen. Sie zerlegt jede Zeile in Felder.

String valueString = value.toString();
String[] SingleCountryData = valueString.split(",");

Hier wird ',' als Trennzeichen verwendet.

Anschließend wird ein Paar aus dem Datensatz am 7. Index des Arrays 'SingleCountryData' und dem Wert '1' gebildet.

output.collect(new Text(SingleCountryData[7]), one);

Wir wählen den Datensatz an Index 7 aus, weil wir Länderdaten benötigen und diese sich an Index 7 im Array 'SingleCountryData' befinden.

Bitte beachten Sie, dass unsere Eingabedaten im folgenden Format vorliegen (wobei das Land an siebter Stelle des Index steht und 0 der Startindex ist):

Transaction_date,Product,Price,Payment_Type,Name,City,State,Country,Account_Created,Last_Login,Latitude,Longitude

Die Ausgabe des Mappers ist wiederum ein Schlüssel-Wert-Paar, das mit der Methode 'collect()' des 'OutputCollector' erzeugt wird.

Erläuterung der SalesCountryReducer-Klasse

In diesem Abschnitt werden wir die Implementierung der SalesCountryReducer-Klasse verstehen.

1. Zunächst legen wir einen Paketnamen für unsere Klasse fest. Unser Paket heißt „SalesCountry“. Bitte beachten Sie, dass die Ausgabedatei der Kompilierung, „SalesCountryReducer.class“, in einem Verzeichnis mit dem Namen dieses Pakets gespeichert wird: „SalesCountry“.

Anschließend importieren wir Bibliothekspakete.

Der folgende Screenshot zeigt eine Implementierung der SalesCountryReducer-Klasse.

Editoransicht der vollständigen Implementierung der Klasse SalesCountryReducer

Code Erläuterung:

1. SalesCountryReducer-Klassendefinition-

public class SalesCountryReducer extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> {

Hierbei handelt es sich bei den ersten beiden Datentypen, 'Text' und 'IntWritable', um die Datentypen der Eingabe-Schlüssel-Wert-Paare für den Reducer.

Die Ausgabe des Mappers hat folgendes Format: , Die Ausgabe des Mappers dient als Eingabe für den Reducer. Um den Datentypen gerecht zu werden, werden hier Text und IntWritable verwendet.

Die letzten beiden Datentypen, 'Text' und 'IntWritable', sind die Datentypen der Ausgabe, die vom Reducer in Form eines Schlüssel-Wert-Paares erzeugt wird.

Jede Reducer-Klasse muss von der MapReduceBase-Klasse abgeleitet werden und das Reducer-Interface implementieren.

2. Definieren der Funktion „Reduzieren“-

public void reduce( Text t_key,
             Iterator<IntWritable> values,                           
             OutputCollector<Text,IntWritable> output,
             Reporter reporter) throws IOException {

Die Eingabe für die reduce()-Methode ist ein Schlüssel mit einer Liste von mehreren Werten.

In unserem Fall wird es zum Beispiel so sein:

, , , , , .

Dies wird dem Reduzierstück übergeben als

Um Argumente dieser Form zu akzeptieren, werden zunächst zwei Datentypen verwendet, nämlich Text und Iterator. Text ist ein Datentyp von Schlüssel und Iterator ist ein Datentyp für eine Liste von Werten für diesen Schlüssel.

Das nächste Argument ist vom Typ OutputCollector. welches den Ausgang der Reduzierstufe auffängt.

Die reduce()-Methode beginnt damit, dass der Schlüsselwert kopiert und der Häufigkeitszähler auf 0 initialisiert wird.

Text key = t_key;
int frequencyForCountry = 0;

Anschließend iterieren wir mithilfe einer 'while'-Schleife durch die Liste der Werte, die dem Schlüssel zugeordnet sind, und berechnen die endgültige Häufigkeit, indem wir alle Werte aufsummieren.

 while (values.hasNext()) {
            // replace type of value with the actual type of our value
            IntWritable value = (IntWritable) values.next();
            frequencyForCountry += value.get();
            
        }

Nun leiten wir das Ergebnis in Form eines Schlüssels und der ermittelten Häufigkeitszählung an den Ausgabekollektor weiter.

Der folgende Code bewirkt Folgendes:

output.collect(key, new IntWritable(frequencyForCountry));

Erläuterung der SalesCountryDriver-Klasse

In diesem Abschnitt werden wir die Implementierung der SalesCountryDriver-Klasse verstehen.

1. Zunächst legen wir einen Paketnamen für unsere Klasse fest. Unser Paket heißt „SalesCountry“. Bitte beachten Sie, dass die Ausgabedatei der Kompilierung, „SalesCountryDriver.class“, in einem Verzeichnis mit dem Namen dieses Pakets gespeichert wird: „SalesCountry“.

Hier ist eine Zeile, die den Paketnamen angibt, gefolgt von Code zum Importieren von Bibliothekspaketen.

Paketdeklaration und Importanweisungen von SalesCountryDriver

2. Definieren Sie eine Treiberklasse, die einen neuen Client-Job und ein neues Konfigurationsobjekt erstellt und Mapper- und Reducer-Klassen ankündigt.

Die Treiberklasse ist dafür verantwortlich, dass unser MapReduce-Job ausgeführt wird HadoopIn dieser Klasse legen wir den Jobnamen, den Datentyp der Ein-/Ausgabe sowie die Namen der Mapper- und Reducer-Klassen fest.

Treibercode, der den Jobnamen, die Schlüssel- und Wertklassen sowie die Formate festlegt

3. Im folgenden Codeausschnitt legen wir Eingabe- und Ausgabeverzeichnisse fest, die zum Konsumieren des Eingabedatensatzes bzw. zum Erzeugen der Ausgabe verwendet werden.

arg[0] und arg[1] sind die Befehlszeilenargumente, die mit einem Befehl in MapReduce hands-on übergeben werden, d.h.

$HADOOP_HOME/bin/hadoop jar ProductSalePerCountry.jar /inputMapReduce /mapreduce_output_sales

Treibercode, der die Eingabe- und Ausgabepfade von der Kommandozeile übergibt.

4. Lösen Sie unseren Job aus

Der folgende Code startet die Ausführung des MapReduce-Jobs:

try {
    // Run the job 
    JobClient.runJob(job_conf);
} catch (Exception e) {
    e.printStackTrace();
}

Häufig gestellte Fragen

Alle drei Klassen importieren org.apache.hadoop.mapred, die ursprüngliche MapReduce-API. Hadoop 3.x wird weiterhin mit dieser API ausgeliefert und verwendet sie, neue Anwendungen basieren jedoch üblicherweise auf org.apache.hadoop.mapreduce, wodurch JobConf und JobClient durch Configuration und Job ersetzt werden.

Modelle, die anhand vergangener Jobhistorien trainiert wurden, prognostizieren die Laufzeit, schlagen Splitgrößen und Reducer-Anzahlen vor und erkennen Abweichungen, bevor ein Lauf abgeschlossen ist. Sie kennzeichnen außerdem Jobs, deren Zähler für übersprungene Datensätze oder fehlgeschlagene Aufgaben außerhalb des üblichen Bereichs liegen.

Copilot erledigt die wiederkehrenden Aufgaben zuverlässig: Klassensignaturen, Generics, Importe und die Treiberkonfigurationsaufrufe. Die Geschäftslogik, beispielsweise welche Spalte das Land enthält, muss jedoch weiterhin von einem Entwickler anhand des tatsächlichen Schemas überprüft werden.

Hadoop schreibt nicht in ein bestehendes Ausgabeverzeichnis, sodass die Ergebnisse nicht überschrieben werden. Löschen Sie mapreduce_output_sales rekursiv mit hdfs dfs -rm -r oder geben Sie beim nächsten Durchlauf einen anderen Ausgabepfad an.

Das JAR-Tool ignoriert eine letzte Manifestzeile ohne Zeilenumbruch, wodurch die Main-Class stillschweigend entfernt wird. Die JAR-Datei lässt sich anschließend fehlerfrei erstellen, schlägt aber zur Laufzeit fehl, da keine Main-Class definiert ist.

Ja. Das Beispiel verwendet Version 2.2.0 in jedem JAR-Dateinamen. Bei einer anderen Version ersetzen Sie diese durch die entsprechende Versionsnummer oder verwenden Sie einfach die Ausgabe von `hadoop classpath`, die alle vom installierten System benötigten JAR-Dateien auflistet.

Nein. Eine pseudo-verteilte Installation auf einem einzelnen Knoten funktioniert unverändert, da die Befehle lediglich voraussetzen, dass HDFS und YARN gestartet sind. Dieselbe JAR-Datei kann ohne Codeänderungen an einen realen Multi-Node-Cluster übermittelt werden.

Ändern Sie den Array-Index im Mapper von 7 auf 5, da „City“ die sechste Spalte der Datei „SalesJan2009.csv“ ist. Kompilieren Sie das Programm neu, erstellen Sie die JAR-Datei neu und führen Sie den Job in einem neuen Ausgabeverzeichnis aus.

Fassen Sie diesen Beitrag mit folgenden Worten zusammen: