Ein Leitfaden für Apache Crunch

Ein Leitfaden für Apache Crunch

1. Einführung

In diesem Tutorial werden wirApache Crunch anhand einer beispielhaften Datenverarbeitungsanwendung demonstrieren. Wir werden diese Anwendung mit demMapReduce-Framework ausführen.

Zunächst werden einige Apache Crunch-Konzepte kurz behandelt. Dann springen wir in eine Beispiel-App. In dieser App werden wir Textverarbeitung durchführen:

  • Zunächst lesen wir die Zeilen aus einer Textdatei

  • Später werden wir sie in Wörter aufteilen und einige gebräuchliche Wörter entfernen

  • Anschließend gruppieren wir die verbleibenden Wörter, um eine Liste der eindeutigen Wörter und ihrer Anzahl zu erhalten

  • Schließlich schreiben wir diese Liste in eine Textdatei

2. Was ist Crunch?

MapReduce ist ein verteiltes, paralleles Programmierframework für die Verarbeitung großer Datenmengen auf einem Servercluster. Software-Frameworks wie Hadoop und Spark implementieren MapReduce.

Crunch provides a framework for writing, testing and running MapReduce pipelines in Java. Hier schreiben wir die MapReduce-Jobs nicht direkt. Vielmehr definieren wir Datenpipeline (d. H. die Operationen zum Ausführen von Eingabe-, Verarbeitungs- und Ausgabeschritten) unter Verwendung der Crunch-APIs. Crunch Planner ordnet sie den MapReduce-Jobs zu und führt sie bei Bedarf aus.

Therefore, every Crunch data pipeline is coordinated by an instance of the Pipeline interface. Diese Schnittstelle definiert auch Methoden zum Einlesen von Daten in eine Pipeline überSource Instanzen und zum Schreiben von Daten aus einer Pipeline inTarget Instanzen.

Wir haben 3 Schnittstellen zur Darstellung von Daten:

  1. PCollection - eine unveränderliche, verteilte Sammlung von Elementen

  2. PTable<K, V> - eine unveränderliche, verteilte, ungeordnete Mehrfachzuordnung von Schlüsseln und Werten

  3. PGroupedTable<K, V> - eine verteilte, sortierte Zuordnung von Schlüsseln vom Typ K zu einemIterable V, die genau einmal wiederholt werden kann

DoFn is the base class for all data processing functions. Es entspricht den KlassenMapper,Reducer undCombiner in MapReduce. Wir verbringen den größten Teil der Entwicklungszeit damit, logische Berechnungen mit. zu schreiben und zu testen

Nachdem wir Crunch besser kennen, können Sie damit die Beispielanwendung erstellen.

3. Crunch-Projekt einrichten

Lassen Sie uns zunächst ein Crunch-Projekt mit Maven einrichten. Wir können dies auf zwei Arten tun:

  1. Fügen Sie die erforderlichen Abhängigkeiten in die Dateipom.xmleines vorhandenen Projekts ein

  2. Verwenden Sie einen Archetyp, um ein Startprojekt zu generieren

Werfen wir einen kurzen Blick auf beide Ansätze.

3.1. Maven-Abhängigkeiten

Um Crunch zu einem vorhandenen Projekt hinzuzufügen, fügen wir die erforderlichen Abhängigkeiten in die Dateipom.xmlein.

Fügen wir zunächst die Bibliothekcrunch-corehinzu:


    org.apache.crunch
    crunch-core
    0.15.0

Fügen Sie als Nächstes die Bibliothekhadoop-clienthinzu, um mit Hadoop zu kommunizieren. Wir verwenden die zur Hadoop-Installation passende Version:


    org.apache.hadoop
    hadoop-client
    2.2.0
    provided

Wir können Maven Central auf die neuesten Versionen der Bibliothekencrunch-core undhadoop-client überprüfen.

3.2. Maven-Archetyp

Another approach is to quickly generate a starter project using the Maven archetype provided by Crunch:

mvn archetype:generate -Dfilter=org.apache.crunch:crunch-archetype

Wenn Sie vom obigen Befehl dazu aufgefordert werden, geben Sie die Crunch-Version und die Details des Projektartefakts an.

4. Crunch Pipeline Setup

Nach dem Einrichten des Projekts müssen wir einPipeline-Objekt erstellen. Crunch has 3 Pipeline implementations:

  • MRPipeline – wird in Hadoop MapReduce ausgeführt

  • SparkPipeline – wird als eine Reihe von Spark-Pipelines ausgeführt

  • MemPipeline – führt In-Memory auf dem Client aus und ist nützlich für Unit-Tests

Normalerweise entwickeln und testen wir mit einer Instanz vonMemPipeline. Später verwenden wir eine Instanz vonMRPipeline oderSparkPipeline für die tatsächliche Ausführung.

Wenn wir eine In-Memory-Pipeline benötigen, können wir die statische MethodegetInstance verwenden, um die Instanz vonMemPipelineabzurufen:

Pipeline pipeline = MemPipeline.getInstance();

Erstellen wir zunächst eine Instanz vonMRPipeline, um die Anwendung mit Hadoop: auszuführen

Pipeline pipeline = new MRPipeline(WordCount.class, getConf());

5. Eingabedaten lesen

Nach dem Erstellen des Pipeline-Objekts möchten wir die Eingabedaten lesen. The Pipeline interface provides a convenience method to read input from a text file,readTextFile(pathName).

Rufen Sie diese Methode auf, um die Eingabetextdatei zu lesen:

PCollection lines = pipeline.readTextFile(inputPath);

Der obige Code liest die Textdatei als Sammlung vonString.

Schreiben wir als nächsten Schritt einen Testfall zum Lesen der Eingabe:

@Test
public void givenPipeLine_whenTextFileRead_thenExpectedNumberOfRecordsRead() {
    Pipeline pipeline = MemPipeline.getInstance();
    PCollection lines = pipeline.readTextFile(INPUT_FILE_PATH);

    assertEquals(21, lines.asCollection()
      .getValue()
      .size());
}

In diesem Test stellen wir sicher, dass wir beim Lesen einer Textdatei die erwartete Anzahl von Zeilen erhalten.

6. Datenverarbeitungsschritte

Nachdem wir die Eingabedaten gelesen haben, müssen wir sie verarbeiten. Crunch API contains a number of subclasses of DoFn to handle common data processing scenarios:

  • FilterFn - filtert Mitglieder einer Sammlung basierend auf einer booleschen Bedingung

  • MapFn - ordnet jeden Eingabedatensatz genau einem Ausgabedatensatz zu

  • CombineFn - kombiniert eine Anzahl von Werten zu einem einzigen Wert

  • JoinFn - führt Verknüpfungen wie innere Verknüpfung, linke äußere Verknüpfung, rechte äußere Verknüpfung und vollständige äußere Verknüpfung aus

Implementieren Sie die folgende Datenverarbeitungslogik mithilfe dieser Klassen:

  1. Teilen Sie jede Zeile in der Eingabedatei in Wörter auf

  2. Entfernen Sie die Stoppwörter

  3. Zählen Sie die eindeutigen Wörter

6.1. Teilen Sie eine Textzeile in Wörter

Erstellen wir zunächst die KlasseTokenizer, um eine Zeile in Wörter aufzuteilen.

Wir werden die KlasseDoFnerweitern. Diese Klasse hat eine abstrakte Methode namensprocess. Diese Methode verarbeitet die Eingabedatensätze vonPCollection und sendet die Ausgabe anEmitter. 

Wir müssen die Aufteilungslogik in dieser Methode implementieren:

public class Tokenizer extends DoFn {
    private static final Splitter SPLITTER = Splitter
      .onPattern("\\s+")
      .omitEmptyStrings();

    @Override
    public void process(String line, Emitter emitter) {
        for (String word : SPLITTER.split(line)) {
            emitter.emit(word);
        }
    }
}

In der obigen Implementierung haben wir die KlasseSplitteraus der BibliothekGuavaverwendet, um Wörter aus einer Zeile zu extrahieren.

Als nächstes schreiben wir einen Komponententest für die KlasseTokenizer:

@RunWith(MockitoJUnitRunner.class)
public class TokenizerUnitTest {

    @Mock
    private Emitter emitter;

    @Test
    public void givenTokenizer_whenLineProcessed_thenOnlyExpectedWordsEmitted() {
        Tokenizer splitter = new Tokenizer();
        splitter.process("  hello  world ", emitter);

        verify(emitter).emit("hello");
        verify(emitter).emit("world");
        verifyNoMoreInteractions(emitter);
    }
}

Der obige Test überprüft, dass die richtigen Wörter zurückgegeben werden.

Lassen Sie uns abschließend die mit dieser Klasse aus der Eingabetextdatei gelesenen Zeilen aufteilen.

DieparallelDo-Methode derPCollection-Schnittstelle wendet die angegebenenDoFn auf alle Elemente an und gibt ein neuesPCollection zurück.

Rufen Sie diese Methode für die Zeilenauflistung auf und übergeben Sie eine Instanz vonTokenizer:

PCollection words = lines.parallelDo(new Tokenizer(), Writables.strings());

Als Ergebnis erhalten wir die Liste der Wörter in der Eingabetextdatei. Wir werden die Stoppwörter im nächsten Schritt entfernen.

6.2. Stoppwörter entfernen

Erstellen Sie ähnlich wie im vorherigen Schritt eineStopWordFilter-Klasse, um Stoppwörter herauszufiltern.

Wir werden jedochFilterFn anstelle vonDoFn erweitern. FilterFn hat eine abstrakte Methode namensaccept. Wir müssen die Filterlogik in dieser Methode implementieren:

public class StopWordFilter extends FilterFn {

    // English stop words, borrowed from Lucene.
    private static final Set STOP_WORDS = ImmutableSet
      .copyOf(new String[] { "a", "and", "are", "as", "at", "be", "but", "by",
        "for", "if", "in", "into", "is", "it", "no", "not", "of", "on",
        "or", "s", "such", "t", "that", "the", "their", "then", "there",
        "these", "they", "this", "to", "was", "will", "with" });

    @Override
    public boolean accept(String word) {
        return !STOP_WORDS.contains(word);
    }
}

Als nächstes schreiben wir den Komponententest für die KlasseStopWordFilter:

public class StopWordFilterUnitTest {

    @Test
    public void givenFilter_whenStopWordPassed_thenFalseReturned() {
        FilterFn filter = new StopWordFilter();

        assertFalse(filter.accept("the"));
        assertFalse(filter.accept("a"));
    }

    @Test
    public void givenFilter_whenNonStopWordPassed_thenTrueReturned() {
        FilterFn filter = new StopWordFilter();

        assertTrue(filter.accept("Hello"));
        assertTrue(filter.accept("World"));
    }

    @Test
    public void givenWordCollection_whenFiltered_thenStopWordsRemoved() {
        PCollection words = MemPipeline
          .collectionOf("This", "is", "a", "test", "sentence");
        PCollection noStopWords = words.filter(new StopWordFilter());

        assertEquals(ImmutableList.of("This", "test", "sentence"),
         Lists.newArrayList(noStopWords.materialize()));
    }
}

Dieser Test überprüft, ob die Filterlogik korrekt ausgeführt wird.

Verwenden Sie abschließendStopWordFilter, um die Liste der im vorherigen Schritt generierten Wörter zu filtern. The filter method of PCollection interface applies the given FilterFn to all the elements and returns a new PCollection.

Rufen Sie diese Methode für die Wortsammlung auf und übergeben Sie eine Instanz vonStopWordFilter:

PCollection noStopWords = words.filter(new StopWordFilter());

Als Ergebnis erhalten wir die gefilterte Sammlung von Wörtern.

6.3. Zählen Sie eindeutige Wörter

Nachdem wir die gefilterte Wortsammlung erhalten haben, wollen wir zählen, wie oft jedes Wort vorkommt. PCollection interface has a number of methods to perform common aggregations:

  • min - gibt das minimale Element der Sammlung zurück

  • max - gibt das maximale Element der Sammlung zurück

  • length - gibt die Anzahl der Elemente in der Auflistung zurück

  • count - Gibt einPTable zurück, das die Anzahl der einzelnen eindeutigen Elemente der Sammlung enthält

Verwenden wir diecount-Methode, um die eindeutigen Wörter zusammen mit ihrer Anzahl zu erhalten:

// The count method applies a series of Crunch primitives and returns
// a map of the unique words in the input PCollection to their counts.
PTable counts = noStopWords.count();

7. Geben Sie die Ausgabe an

Als Ergebnis der vorherigen Schritte haben wir eine Tabelle mit Wörtern und deren Anzahl. Wir wollen dieses Ergebnis in eine Textdatei schreiben. The Pipeline interface provides convenience methods to write output:

void write(PCollection collection, Target target);

void write(PCollection collection, Target target,
  Target.WriteMode writeMode);

 void writeTextFile(PCollection collection, String pathName);

Rufen wir daher diewriteTextFile-Methode auf:

pipeline.writeTextFile(counts, outputPath);

8. Pipeline-Ausführung verwalten

Alle bisherigen Schritte haben gerade die Datenpipeline definiert. Es wurde keine Eingabe gelesen oder verarbeitet. Dies liegt daran, dassCrunch uses lazy execution model.

Die MapReduce-Jobs werden erst ausgeführt, wenn eine Methode zur Steuerung der Jobplanung und -ausführung auf der Pipeline-Schnittstelle aufgerufen wird:

  • run - erstellt einen Ausführungsplan, um die erforderlichen Ausgaben zu erstellen, und führt ihn dann synchron aus

  • done - führt alle verbleibenden Jobs aus, die zum Generieren von Ausgaben erforderlich sind, und bereinigt dann alle erstellten Zwischendatendateien

  • runAsync - ähnelt der Ausführungsmethode, wird jedoch nicht blockierend ausgeführt

Rufen wir daher die Methodedoneauf, um die Pipeline als MapReduce-Jobs auszuführen:

PipelineResult result = pipeline.done();

Die obige Anweisung führt die MapReduce-Jobs aus, um die Eingaben zu lesen, zu verarbeiten und das Ergebnis in das Ausgabeverzeichnis zu schreiben.

9. Pipeline zusammenbauen

Bisher haben wir die Logik entwickelt und getestet, um Eingabedaten zu lesen, zu verarbeiten und in die Ausgabedatei zu schreiben.

Als nächstes setzen wir sie zusammen, um die gesamte Datenpipeline zu erstellen:

public int run(String[] args) throws Exception {
    String inputPath = args[0];
    String outputPath = args[1];

    // Create an object to coordinate pipeline creation and execution.
    Pipeline pipeline = new MRPipeline(WordCount.class, getConf());

    // Reference a given text file as a collection of Strings.
    PCollection lines = pipeline.readTextFile(inputPath);

    // Define a function that splits each line in a PCollection of Strings into
    // a PCollection made up of the individual words in the file.
    // The second argument sets the serialization format.
    PCollection words = lines.parallelDo(new Tokenizer(), Writables.strings());

    // Take the collection of words and remove known stop words.
    PCollection noStopWords = words.filter(new StopWordFilter());

    // The count method applies a series of Crunch primitives and returns
    // a map of the unique words in the input PCollection to their counts.
    PTable counts = noStopWords.count();

    // Instruct the pipeline to write the resulting counts to a text file.
    pipeline.writeTextFile(counts, outputPath);

    // Execute the pipeline as a MapReduce.
    PipelineResult result = pipeline.done();

    return result.succeeded() ? 0 : 1;
}

10. Hadoop-Startkonfiguration

Die Datenpipeline ist somit bereit.

Wir benötigen jedoch den Code, um es zu starten. Schreiben wir daher diemain-Methode, um die Anwendung zu starten:

public class WordCount extends Configured implements Tool {

    public static void main(String[] args) throws Exception {
        ToolRunner.run(new Configuration(), new WordCount(), args);
    }

ToolRunner.run analysiert die Hadoop-Konfiguration über die Befehlszeile und führt den MapReduce-Job aus.

11. Anwendung ausführen

Die vollständige Bewerbung ist nun fertig. Führen Sie den folgenden Befehl aus, um es zu erstellen:

mvn package

Als Ergebnis des obigen Befehls erhalten wir die gepackte Anwendung und ein spezielles Job-JAR im Zielverzeichnis.

Verwenden Sie dieses Job-JAR, um die Anwendung auf Hadoop auszuführen:

hadoop jar target/crunch-1.0-SNAPSHOT-job.jar  

Die Anwendung liest die Eingabedatei und schreibt das Ergebnis in die Ausgabedatei. Die Ausgabedatei enthält eindeutige Wörter mit einer ähnlichen Anzahl wie die folgenden:

[Add,1]
[Added,1]
[Admiration,1]
[Admitting,1]
[Allowance,1]

Zusätzlich zu Hadoop können wir die Anwendung in IDE, als eigenständige Anwendung oder als Komponententest ausführen.

12. Fazit

In diesem Tutorial haben wir eine Datenverarbeitungsanwendung erstellt, die auf MapReduce ausgeführt wird. Mit Apache Crunch können MapReduce-Pipelines auf einfache Weise in Java geschrieben, getestet und ausgeführt werden.

Wie üblich kann der vollständige Quellcodeover on Github gefunden werden.