Der Begriff MapReduce ist bei vielen automatisch mit Apache Hadoop verbunden. Apache Hadoop ist ein Framework, mit welchem sich MapReduce Berechnungen auf sehr großen Datenbeständen effizient über viele Rechnerknoten skalieren lassen. Wer allerdings einen Blick in die Dokumentation von Hadoop wirft, wird feststellen, dass das Setup selbst bei einer Single-Node Implementierung relativ komplex ist.

Dieser initiale Aufwand schreckt viele – auch auf Grund von Zeitmangel und fehlenden Anforderungen – ab.

Glücklicherweise lassen sich MapReduce Berechnungen nicht nur mit Hadoop umsetzen. In diesem Artikel soll gezeigt werden, wie sich dies mit der MongoDB, Spring Boot und Spring Data MongoDB in 60 Minuten vom Projekt-Setup bis zur fertigen Anwendung inklusive Test umsetzen lässt.

Bevor es an die eigentliche Umsetzung geht, soll zunächst die Funktionsweise von MapReduce vorgestellt werden.

MapReduce

MapReduce ist ein Programmiermodell bzw. Aggregationsverfahren, welches von Google im Jahr 2004 vorgestellt wurde. Das Verfahren besteht aus den Schritten Map, Shuffle und Reduce.

Für gewöhnlich werden im Map-Schritt die relevanten Daten ermittelt und als Key-Value Paare gesammelt. Dies kann verteilt auf vielen Rechnerknoten parallel stattfinden.

Im Shuffle-Schritt werden die gesammelten Paare anhand der Keys gruppiert (und ggf. Daten über Rechnerknotengrenzen hinweg ausgetauscht), sodass ein Verarbeitungsknoten auf alle Values eines Keys zugreifen kann.

Im Reduce-Schritt werden die gruppierten Paare entsprechend der Algorithmusimplementierung verarbeitet / aggregiert.

Beispiel

Es sei ein fiktiver Anwendungsfall gegeben, bei welchem Meta-Daten von HttpRequests einer stark frequentierten Internetseite in eine MongoDB geloggt werden.

Mittels MapReduce Algorithmus soll ermittelt werden, wie viele Anfragen die Internetseite von welchem Betriebssystem (Windows, Linux, Mac OS) und welchem Web-Browser (Firefox, Chrome, Opera) erhalten hat.

Informationen über das Betriebssystem oder der verwendete Browser eines Web-Clients werden für gewöhnlich bei Browseraufrufen im Header als ‘User Agent’ Parameter mitgesendet und sehen in der Regel folgendermaßen aus:

Mozilla/5.0 (X11; Linux x86_64; rv:44.0) Gecko/20100101 Firefox/44.0

Das nachfolgende Schaubild zeigt beispielhaft, wie Daten durch den Algorithmus Schritt für Schritt gesammelt, gruppiert und aggregiert werden.

Spalte 1 zeigt den Gesamtdatenbestand.
Spalte 2 zeigt die gesammelten (Map) und gruppierten (Shuffle) Daten.
Spalte 3 zeigt die aggregierten Summen.

MapReduce Example

Architektur und Informationsfluss

Für die Umsetzung des Anwendungsfalls als Spring Boot Anwendung bietet sich eine Zwei-Schichten-Architektur, bestehend aus einer Service- und Datenzugriffsschicht, an. Diese werden als Spring Service-Klasse und als Data Access Object (DAO) implementiert. Für erweiterte Zugriffsmöglichkeiten (z.B. Map-Reduce) kann das MongoTemplate und ein Ergebniskonverter eingesetzt werden.

Schematische Darstellung des Informationsflusses

Um den Anwendungsfall später testen zu können müssen initial Testdaten mit dem DAO in der Datenbank angelegt werden (1). Die Testdaten werden dem Service in Form von POJOs übergeben, vom DAO in JSON-Objekte übersetzt und an die Datenbank übermittelt (2).

Die MapReduce Funktionen werden beim Aufruf zur Berechnung vom Service an das MongoTemplate und die Datenbank weitergeleitet (3, 4). Als Ergebnis liefert die Datenbank ein JSON Objekt zurück, welches von einem Ergebniskonverter wieder in ein Java-Objekt gemappt wird (5). Das MongoTemplate liefert dem aufrufenden Service die konvertierten Daten (6) zurück.

Implementierung

Bei der nachfolgenden Implementierung wird ein JDK 1.8, die Spring Tool Suite (STS, Eclipse IDE mit Spring Erweiterungen) und ein Gradle-Plugin verwendet. Das Projekt kann aber ebenso z.B. mit Maven und einer anderen IDE implementiert werden.

Die MongoDB kann zu Testzwecken als Docker-Container betrieben und mit den folgenden Befehlen geladen, gestoppt und wieder gestartet werden (<<name>> ist durch einen passenden Namen zu ersetzen):

1. Projektsetup

Für das Projektsetup kann der “Spring Initializr” verwendet werden. Es wird eine Spring Boot Anwendung, mit allen notwendigen Abhängigkeiten, mit folgenden Parametern erstellt:

– Generate as “Gradle Project”
– Spring Boot Version: 1.3.6
– Dependencies: “MongoDB”, “Lombok”

Das generierte Artefakt kann heruntergeladen und in die IDE importiert werden.

Spring Initializr generated artifacts

Informationen zur Integration von Lombok in der IDE können der Lombok Dokumentation entnommen werden. Alternativ können Getter / Setter, etc. auch von Hand implementiert werden.

2. Konfiguration von Verbindungsparametern

Nach dem importieren des Projektes in der IDE kann in der Datei src/main/resources/application.properties der Name der Datenbank und ggf. Netzwerk-Host / -Port eingetragen werden. Falls noch keine Datenbank angelegt wurde, so kann dies mit einem der zuvor erwähnten Datenbanktools wie z.B. Robomongo durchgeführt werden.

3. Implementierung des Datenmodells

Das Datenmodell für den Anwendungsfall besteht aus genau einer Klasse „HttpRequest“. Diese ist durch die @Document Annotation als Datenbank-Collection klassifiziert (analog zu @Entity bei JPA). Die @Data Annotation sorgt dafür, dass automatisch getter, setter, equals(), hashCode() und toString() Methoden im Compile-Vorgang generiert werden.

4. Implementierung der Map-Funktion

Bei MongoDB werden Map und Reduce Funktionen als JavaScript-Funktionen implementiert. Es empfielt sich diese außerhalb einer Anwendung mit einem Datenbanktool wie Robomongo zu entwickeln bzw. zu testen. Dadurch wird die Fehlersuche deutlich verkürzt und vereinfacht.

Spring Data MongoDB kann direkt auf Classpath-Resources zugreifen. Hierfür wird eine JavaScript-Datei mit dem Namen map.js mit dem nachfolgend dargestellten Inhalt erstellt (src/main/resources/map.js). 

JavaScript-Funktionen können auch direkt in der MongoDB hinterlegt werden (Stichwort: JavaScript Functions on ther Server, ExecutableMongoScript). Aus Gründen der Übersichtlichkeit wurde an dieser Stelle darauf verzichtet.

5. Implementierung der Reduce-Funktion

Analog zur Map-Function nachfolgend der Aufbau der Reduce-Funktion. Übergabeparameter sind der Key, nach dem gruppiert wurde (OS) und die gruppierten Elemente (Browser) als Array. Diese werden jeweils aufsummiert (src/main/resources/reduce.js).

6. Implementierung einer Ergebnis-Klasse

Analog zur Datenmodell-Klasse, nur ohne @Document Annotation, wird eine Result-Bean definiert. Die Daten aus der Abfrage werden im nächsten Schritt durch einen Konverter in die Result-Bean gemappt.

7. Erstellung eines Konverters

Ergebnisse von MapReduce-Operationen werden von Spring Data nicht automatisch in Ergebnisklassen gemappt. Die Datenbank liefert als Aggregationsergebnis JSON-Objekte mit Key-Value Paaren zurück. Das Key-Attribut hat den Namen „id“ und repräsentiert den Key aus dem Map-Schritt (map.js). Das Value-Attribut ist entweder eine Map<String, Object> (reduce.js) oder der ermittelte Wert des Map-Schrittes (wenn es nur ein Element für diesen Key gab, sodass der Reduce-Schritt gar nicht ausgeführt wurde). Die Werte können wie gezeigt in das Result-Objekt gemappt werden.

8. Registrieren des Konverters im Application Context

Damit der Konverter beim Ausführen der MapReduce Operation berücksichtigt wird, muss dieser dem Application Context bekannt gemacht werden. Hierfür muss eine Konfiguration erstellt werden. Alle sonstigen Attribute können durch injizierte Objekte übernommen werden.

9. Implementierung eines Data Access Objekts für HttpRequest Objekte

Durch die Verwendung von Spring Data MongoDB stehen für die Kommunikation mit der MongoDB sowohl Repositories (DAOs) als auch das MongoTemplate (erweiterte Abfragen) zur Verfügung. Um Testdaten in die Datenbank speichern zu können, wird das Inteface HttpRequestDao erstellt. Dieses erbt vom Typ MongoRepository. Als Generische-Typen werden die Dokumentenklasse (HttpRequest) und der ID-Typ (z.B. BigInteger) angegeben. Anhand dieser Informationen erkennt Spring Data MongoDB, wie Daten in die Datenbank geschrieben und beim Auslesen wieder gemappt werden müssen.

10. Implementierung eines Services zur Ausführung der MapReduce Operation:

Eine Serviceklasse wird durch die @Service Annotation im Spring Context registriert, sodass sie später per Dependency-Injection automatisch injiziert werden kann. Zum Löschen und Speichern von Objekten kann Standardfunktionalität des DAOs (bzw. des Mongo-Repositories) verwendet werden.

Damit das Ergebnis beim Aufruf der MapReduce-Berechnung zurückgegeben wird, muss der Output-Typ auf inline gesetzt werden. Hierbei darf die Ergebnismenge nicht größer als 16 MB sein. Alternativ, wenn größere Datenmengen errechnet werden sollen, können diese auch in extra Datenbank-Collections („Tabellen“) abgelegt werden.

11. Testen des MapReduce-Algorithmus

Zum Testen des Algorithmus kann der bereits angelegte JUnit Integration-Test im Verzeichnis src/test/java benutzt werden. Testdaten können in einer @Before Annotation markierten Methode in der Datenbank angelegt werden.

Fazit

Mit der MongoDB, Spring Boot und Spring Data MongoDB lassen sich schnell, einfach und mit wenig Aufwand MapReduce Algorithmen implementieren. Sind zu analysierende Daten bereits in einer MongoDB gespeichert, können Daten ohne weitere Tools aggregiert werden.

Parallelisierung, und damit eine beschleunigte Berechnung, ist durch Sharding der Datenbank möglich.

Da die Implementierung der Map und Reduce Funktionen mittels JavaScript erfolgen, hat man keine Typ-Sicherheit und nur sehr eingeschränkte IDE-Unterstützung. Man muss sich im „Trial and Error“-Verfahren und Debugging an einen Algorithmus herantasten.

Für einfachere Aggregationen ist unter Umständen das Aggregation-Framework der MongoDB geeigneter und effizienter. Um einen Eindruck von MapReduce zu bekommen, ist die MongoDB aber auf jeden Fall hervorragend geeignet.

Leave a Comment

By continuing to use the site, you agree to the use of cookies. more information

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.

Close