[TOC]
Es existieren zwei Endpoints, welche im ZipDataController
definiert sind:
GET /city/{zipCode}
gibt die den Stadtnamen und Staat der Postleitzahl zurück.GET /zip/{cityName}
gibt eine Liste aller Postleitzahlen zurück, dessen StadtnamecityName
entspricht.
Der REST-API Controller PlzDataController
erhält über Dependency-Injection einen ICityService
übergeben.
Dieses Interface stellt zwei Methoden bereit:
City? GetCityFromZip(string zip);
IEnumerable<string> GetZipsFromCity(string city);
Dieses Interface wird jeweils einmal pro Datenbank implementiert.
So gibt es einen MongoCityService
, RedisCityService
und CassandraCityService
.
Einer dieser Implementationen (wie in appsettings.json
spezifiziert) wird beim Initialisieren der
Anwendung in der Klasse Startup
in den Konstruktor des Controllers injiziert.
Dafür kann in den appsettings.json
ein Wert für Db
gewählt werden.
Mögliche Werte sind Redis
, MongoDB
und Cassandra
.
Des weitern wurde Unterstützung für docker compose hinzugefügt, welche es erlauben alle benötigten Docker Container in einer Datei zu konfigurieren und diese anschließend mit einem Befehl zu starten.
docker-compose-local-dev.yml
startet die services im docker container und mapped die jeweiligen Ports zum Host, sodass die API auf vom Host auf die Container zugreifen kann.docker-compose.yml
baut die Api mithilfe einer Dockerfile und startet diese in einem Container. Die benötigten Services werden ebenfalls in als Container gestartet, die Ports werden aber nur innerhalb des Netzwerks exposed, anstatt diese zum Host zu mappen. Der Port 8000 wird zum Host gemappt, somit kann die API über diesen erreicht werden. Aktuell wird Swagger in der Variante nicht unterstützt
Als Benchmark wird eine Folge von blockierenden Datenbank abfragen verwendet.
- Zips von den folgenden Städten
HAMBURG
,EAST LIVERMORE
,PINEHURST
,JEFFERSON
- Stadtnamen der folgenden Zips
55339
,76384
,83455
,93644
Zum ausführen der Benchmark docker-compose -f docker-compose-local-dev.yml up
ausführen, anschließen Benchmark
bauen und starten.
Testsystem: I7-8700k, DDR4 RAM, NVMe SSD
Method | Mean | Error | StdDev |
---|---|---|---|
BenchRedis | 5.950 ms | 0.1175 ms | 0.0981 ms |
BenchMongo | 86.985 ms | 1.6582 ms | 1.7743 ms |
BenchCassandra | 222.030 ms | 2.0370 ms | 1.8057 ms |
Entwicklung mit Redis:
- Loc 95
- Aufwendiger als Mongo DB. Der Unterschied resultiert aus dem in der Datenbank nicht vorhandem Datenmodell. Das Mappen von C# Model Klassen auf die Datenbank muss manuell implementiert werden. Die gegebenen Daten konnten nicht in einem Dokument gespeichert werden, sondern mussten jeweils mit verschiedenen key/value Kombinationen angelegt und abgefragt werden.
Entwicklung mit Mongo:
- Loc: 60
- Weniger aufwendig als Redis DB: Es wurden 60 LoC benötigt. Das JSON Format und die Möglichkeit alle Daten in einem Dokument zu speichern verinfachten sowohl das Speichern als auch das Abfragen der Daten. Der Treiber übernimmt sowohl das Mappen von Model Klassen auf die Datenbank als auch von der Datenbank auf die Model Klassen.
Entwicklung mit Cassandra:
- Loc: 60
- Ähnlich aufwendig wie MongoDB. Hier gibt es allerdings nur ein automatisches Mapping bei Query Abfragen, also von der Datenbank auf Model Klassen. Das Interface zur Abfrage und zum Einfügen ist bei Mongo leichter zu bedienen.
Die Aufgabe wurde in Form einer REST-API gelöst.
Die Dokumentation dieser ist unter anderem mithilfe von Swagger auf Port 31474
(http://localhost:31474/swagger
) vorzufinden.
In der Redis Datenbank werden dafür die folgenden Daten importiert:
- Key:
{zip}.state
Value: Der Staat, in dem sich die Stadt mit der Postleitzahl{zip}
befindet - Key:
{zip}.city
Value: Der Name der Stadt mit der Postleitzahl{zip}
- Key:
{city}.zip
Value: Eine Liste aller Postleitzahlen mit dem Stadtnamen{city}
- Start Neo4j (graph db):
docker run \
--name our-neo4j \
-p7474:7474 -p7687:7687 \
--env NEO4J_AUTH=neo4j/test \
neo4j:latest
Browser runs on localhost:7474
username: neo4j pw: test
- Run script of
ModulesAlexander.cypher
/ModulesHugo.cypher
file in Neo4j browser Interface
- Aufgabe 5b 1.): Welche Module sind für NoSQL/BigData nützlich?
MATCH (c1:Course)-[:USED_IN]->(c2:Course) WHERE c2.name = "NOSQL" RETURN DISTINCT c1.name
- Aufgabe 5b 2.): Welche Module wurden bisher im Studium nicht wieder genutzt? Anders formuliert: Welche Knoten haben keinen Nachfolgeknoten?
MATCH (c1:Course) WHERE NOT exists((c1)-[:USED_IN]->()) RETURN DISTINCT c1.name`
- Open bash on container and remove folders in data folder:
docker exec -it our-neo4j /bin/bash
- Import of data via docker cp command:
docker cp /Users/alexander.koenemann/IdeaProjects/NoSQLWP/nosqlHugoAlex/Aufgabe6-graph-db/neo4j-v4-data/. our-neo4j:data/
- Query:
MATCH (n {id : "/c/en/baseball"})-[r:IsA]-(result) RETURN result.id
Für die Aufgabe wird weiterhin die REST-API aus dem vorherigen Aufgabenblatt verwendet.
In der MongoDB wird pro Datensatz ein Dokument angelegt, welches die folgenden Felder enthält:
Id
(Von Mongo generiert)Name
(Name der Stadt)State
(Der Staat in dem sich die Stadt befindet)Zip
(Der Zip-Code der Stadt)
- Starten der container:
- Variante 1: API in Container
docker-compose up
baut und startet die API; startet die Mongo, Redis und Cassandra DB Container.
- DB Abfrage über curl
- get city and state: 'curl http://localhost:8000/city/01001'
- get zip: 'curl http://localhost:8000/zip/Hamburg'
- Variante 2: API auf Host
docker-compose -f docker-compose-local-dev.yml up
startet die Mongo, Redis und Cassandra DB Container.- Bauen und starten von
Api
- Swagger über 'http://localhost:5000/swagger/index.html' aufrufen
- Variante 1: API in Container
Um Dopplungen zu reduzueren ist der Vergleich mit den anderen Datenbanken in Abschnitt Aufbau der aufgabenbergreifenden Api dokumentiert.
- Starten der shell:
docker exec -it {container name} bash
mongo
- Löschen der Daten:
db.getCollection("fussball").drop()
- Zählen der Daten:
db.getCollection("fussball").find().count()
a) Einfügen der Daten
db.fussball.insertMany([
{name: 'HSV', gruendung: new Date(1888, 09, 29), farben: ['weiss', 'rot'], Tabellenplatz: 17, nike: 'n'},
{name: 'Dortmund', gruendung: new Date(1909, 12, 19), farben: ['gelb', 'schwarz'], Tabellenplatz: 16, nike: 'n'},
{name: 'Schalke', gruendung: new Date(1904, 5, 4), farben: ['blau'], Tabellenplatz: 15, nike: 'n'},
{name: 'Paderborn', gruendung: new Date(1907, 8, 14), farben:['blau', 'weiss', ], Tabellenplatz:14, nike:'n', },
{name: 'Hertha', gruendung: new Date(1892, 7, 25), farben: ['blau', 'weiss'], Tabellenplatz: 13, nike: 'j'},
{name: 'Augsburg', gruendung: new Date(1907, 8, 8), farben: ['rot', 'weiss'], Tabellenplatz: 12, nike: 'j'},
{name: 'Pauli', gruendung: new Date(1910, 5, 15), farben: ['braun', 'weiss'], Tabellenplatz: 11, nike: 'n'},
{name: 'Gladbach', gruendung: new Date(1900, 8,1), farben: ['schwarz', 'weiss', 'gruen'], Tabellenplatz: 10, nike: 'n'},
{name: 'Frankfurt', gruendung: new Date(1899, 3, 8), farben: ['rot', 'schwarz', 'weiss'], Tabellenplatz: 9, nike: 'j'},
{name: 'Leverkusen', gruendung: new Date(1904, 11, 20, 16, 15), farben: ['rot', 'schwarz'], Tabellenplatz: 8, nike: 'n'},
{name: 'Stuttgart', gruendung: new Date(1893, 9, 9 ), farben: ['rot', 'weiss'], Tabellenplatz: 7, nike: 'n'},
{name: 'Werder', gruendung: new Date(1899,2,4), farben: ['gruen','weiss'], Tabellenplatz: 6, nike: 'j'}
]);
- alle Vereine, mit Namen "Augsburg"
db.getCollection("fussball").find({ "name" : "Augsburg" })
- alle Nike-Vereine, welche schwarz als mindestens eine Vereinsfarbe haben
db.getCollection("fussball").find({ "nike" : "j", "farben" : { $all : [ "schwarz" ] } })
- alle Nike-Vereine, welche weiss und grün als Vereinsfarbe haben
db.getCollection("fussball").find({ "nike" : "j", "farben" : { $all : [ "weiss", "gruen" ] } })
- alle Nike-Vereine, welche weiss oder grün als Vereinsfarbe haben
db.getCollection("fussball").find(
{ "nike" : "j",
$or : [
{ "farben" : { $all : [ "gruen" ] } },
{ "farben" : { $all : [ "weiss" ] } }
]
}
);
- den Verein mit dem höchsten Tabellenplatz
db.getCollection("fussball").find().sort({ Tabellenplatz : 1 }).limit(1)
- alle Vereine, die nicht auf einem Abstiegsplatz stehen
db.getCollection("fussball").find({ Tabellenplatz : { $lt : 15 } })
- Erstellen Sie eine beliebige andere sinnvolle Abfrage und unterdrücken Sie dabei die Ausgabe des _id Feldes
db.getCollection("fussball").find({ gruendung : { $gt : new Date(1900, 1, 1) } }, { _id : 0 })
Führen sie folgende Änderungsoperation aus:
db.fussball.update({ name : 'Augsburg' }, { Tabellenplatz : 1 })
Was beobachten sie als Ergebnis?
{ "_id" : ObjectId("618bfaafa0817d590df179df"), "Tabellenplatz" : 1 }
Das komplette Objekt wird durch das übergebene ("Tabellenplatz" : 1
) ersetzt. Die id bleibt jedoch gleich.
- Ändern sie den Tabellenplatz von Leverkusen auf 2
db.fussball.update({ name : "Leverkusen" }, { $set : { Tabellenplatz : 2 } })
- Werder soll um einen Tabellenplatz nach vorne gebracht werden
db.fussball.update({ name : "Leverkusen" }, { $inc : { Tabellenplatz : - 1 } })
- Ergänzen sie für den HSV ein Attribut „abgestiegen“ mit einem sinnvollen Wert
db.fussball.update({ name : "HSV" }, { $set : { "abgestiegen" : 1 } })
- Ergänzen sie für alle Vereine, deren Vereinsfarbe weiss enthält, ein Attribut "Waschtemperatur" mit dem Wert 90.
db.fussball.update({ "farben" : { $all : ["weiss"] } }, { $set : { "Waschtemperatur" : 90 } }, { multi : true } )
- Starten der shell:
docker compose up
startet die Mongo, Redis und Cassandra DB container.
Um Dopplungen zu reduzueren ist der Vergleich mit den anderen Datenbanken in Abschnitt Aufbau der aufgabenbergreifenden Api dokumentiert.
- Hinzufügen einer column in die bestehende Table:
ALTER TABLE CITY ADD Fussball text
- Werte für Fussball für die Städte Bremen und Hamburg eintragen:
UPDATE CITY SET Fussball = 'Ja' WHERE name = 'BREMEN'
UPDATE CITY SET Fussball = 'Ja' WHERE name = 'HAMBURG'
Grundlage: Klonen eiens Repositories für das Erstellen des Hadoop Containers:
git clone https://github.com/big-data-europe/docker-hadoop.git
Starten des Docker Containers:
docker-compose up
Testen der Hadoop Umgebung mit einem MapReduce Job auf Basis eines Beispielprogramms für Map Reduce:
- Quelle der .jar Datei: https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-mapreduce-examples/2.7.1/hadoop-mapreduce-examples-2.7.1-sources.jar
- Kopieren der .jar Datei in die Namenode des Containers (Die .jar liegt im Aufgabenordner unseres Repos):
docker cp hadoop-mapreduce-examples-2.7.1-sources.jar namenode:/tmp/
- Als Textinput wird der erste Band von Harry Potter verwendet:
harryPotter_wordcount_example.txt
- Kopieren der Datei in die Namenode des Containers (Die Quelldatei liegt im Aufgabenordner unseres Repos):
docker cp harryPotter_wordcount_example.txt namenode:/tmp/
- Die Datei ist jetzt für den Container sichtbar und kann über ein Terminal in den entsprechenden Zielordner kopiert werden:
- Shell auf der namenode öffnen:
docker exec -it namenode /bin/bash
- Erstellen eines Inputfolders im HDFS Dateisystem (-p steht für parent folder with children):
hdfs dfs -mkdir -p /user/root/input
- Verschieben des Textdokumentes in der HDFS Input Ordner:
hdfs dfs -put tmp/harryPotter_wordcount_example.txt /user/root/input
- Parameter: Class: (<...>.jar), Funktion: WordCount, Input, Output
hadoop jar hadoop-mapreduce-examples-2.7.1-sources.jar org.apache.hadoop.examples.WordCount input output
Anzeige der Outputdateien und Ergebnis:
hdfs dfs -ls /user/root/output
-rw-r--r-- 3 root supergroup 0 2021-11-28 11:15 /user/root/output/_SUCCESS
-rw-r--r-- 3 root supergroup 121427 2021-11-28 11:15 /user/root/output/part-r-00000
Ergebnisdatei ausgeben:
hdfs dfs -cat /user/root/output/part-r-00000
Ergebnisdatei in lokales Dateisystem kopieren
hdfs dfs –copyToLocal /user/root/output/part-r-00000 /tmp
docker cp namenode:/tmp/part-r-00000 /Users/alexander.koenemann/IdeaProjects/NoSQLWP/nosqlHugoAlex/Aufgabe-11-MapReduce/
Ergebnisdatei mit Python Script auswerte, um TOP 10 Wörter aus dem ersten Band anzuzeigen:
python3 findMaxInWordcountOutput.py
Ergebnis der TOP 10 Wörter aus Harry Potter 1:
the: 3654
and: 2139
to: 1827
a: 1578
Harry: 1254
of: 1233
was: 1150
he: 1020
in: 898
his: 892