Kapitel 1. Einführung in Apache Spark: Eine einheitliche Analyse-Engine

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

In diesem Kapitel werden die Ursprünge von Apache Spark und die zugrundeliegende Philosophie dargelegt. Es gibt auch einen Überblick über die wichtigsten Komponenten des Projekts und seine verteilte Architektur. Wenn du mit der Geschichte von Spark und den grundlegenden Konzepten vertraut bist, kannst du dieses Kapitel überspringen.

Die Entstehung von Spark

In diesem Abschnitt werden wir die kurze Entwicklung von Apache Spark nachzeichnen: seine Entstehung, seine Inspiration und seine Annahme in der Community als de facto Big Data Unified Processing Engine.

Big Data und verteiltes Rechnen bei Google

Wenn wir an Größe denken, denken wir unweigerlich an die Fähigkeit von Googles Suchmaschine, die Daten der Welt im Internet blitzschnell zu indizieren und zu durchsuchen. Der Name Google ist ein Synonym für Größe. Tatsächlich ist Google eine absichtliche Falschschreibung des mathematischen Begriffs googol: Das ist 1 plus 100 Nullen!

Weder herkömmliche Speichersysteme wie relationale Datenbankmanagementsysteme (RDBMS) noch zwingende Programmiermethoden waren in der Lage, den Umfang zu bewältigen, in dem Google die indizierten Dokumente des Internets aufbauen und durchsuchen wollte. Der daraus resultierende Bedarf an neuen Ansätzen führte zur Schaffung des Google File System (GFS), MapReduce (MR) und Bigtable.

Während GFS ein fehlertolerantes und verteiltes Dateisystem für viele Commodity-Hardware-Server in einer Cluster-Farm bot, ermöglichte Bigtable eine skalierbare Speicherung von strukturierten Daten über GFS. MR führte ein neues paralleles Programmierparadigma ein, das auf funktionaler Programmierung basiert und die Verarbeitung großer Datenmengen über GFS und Bigtable ermöglicht.

Im Wesentlichen interagieren deine MR-Anwendungen mit dem MapReduce-System, das den Berechnungscode (Map- und Reduce-Funktionen) dorthin schickt, wo die Daten liegen, wodurch die Datenlokalität und die Affinität zum Cluster-Rack bevorzugt werden, anstatt die Daten zu deiner Anwendung zu bringen.

Die Worker im Cluster aggregieren und reduzieren die Zwischenberechnungen und erzeugen eine endgültige Ausgabe der Reduktionsfunktion, die dann in eine verteilte Speicherung geschrieben wird, wo sie für deine Anwendung zugänglich ist. Dieser Ansatz reduziert den Netzwerkverkehr erheblich und sorgt dafür, dass der größte Teil der Ein- und Ausgabe (E/A) lokal auf der Festplatte bleibt, anstatt über das Netzwerk verteilt zu werden.

Die meiste Arbeit, die Google geleistet hat, war proprietär, aber die Ideen, die in den drei oben genannten Papieren zum Ausdruck kamen, haben andere innovative Ideen in der Open-Source-Community beflügelt - vor allem bei Yahoo!, das sich mit ähnlichen Big-Data-Herausforderungen für seine Suchmaschine beschäftigte.

Hadoop bei Yahoo!

Die rechnerischen Herausforderungen und Lösungen, die in Googles GFS-Papier beschrieben wurden, lieferten die Vorlage für das Hadoop File System (HDFS), einschließlich der MapReduce-Implementierung als Rahmenwerk für verteiltes Rechnen. Es wurde im April 2006 der Apache Software Foundation (ASF), einer herstellerunabhängigen Non-Profit-Organisation, zur Verfügung gestellt und wurde Teil des Apache Hadoop Frameworks mit verwandten Modulen: Hadoop Common, MapReduce, HDFS und Apache Hadoop YARN.

Obwohl Apache Hadoop auch außerhalb von Yahoo! weit verbreitet war und eine große Open-Source-Gemeinschaft von Mitwirkenden sowie zwei Open-Source-basierte kommerzielle Unternehmen (Cloudera und Hortonworks, die inzwischen fusioniert sind) inspiriert hatte, wies das MapReduce-Framework auf HDFS einige Schwächen auf.

Erstens war es schwer zu verwalten und zu administrieren, was zu einer hohen Komplexität im Betrieb führte. Zweitens war die allgemeine MapReduce-API für die Batch-Verarbeitung sehr umfangreich und erforderte eine Menge Standard-Setup-Code mit einer brüchigen Fehlertoleranz. Drittens wird bei großen Batches von Datenaufträgen mit vielen Paaren von MR-Aufgaben das Zwischenergebnis jedes Paares für die nachfolgende Phase des Vorgangs auf die lokale Festplatte geschrieben (siehe Abbildung 1-1). Diese wiederholte Durchführung der Festplatten-E/A forderte ihren Tribut: Große MR-Aufträge konnten stundenlang oder sogar tagelang laufen.

Intermittent iteration of reads and writes between map and reduce computations
Abbildung 1-1. Intermittierende Iteration von Lese- und Schreibvorgängen zwischen Map- und Reduce-Berechnungen

Und schließlich war Hadoop MR zwar für große Aufträge zur allgemeinen Stapelverarbeitung geeignet, aber nicht für die Kombination anderer Arbeitslasten wie maschinelles Lernen, Streaming oder interaktive SQL-ähnliche Abfragen.

Um diese neuen Arbeitslasten zu bewältigen, entwickelten Ingenieure maßgeschneiderte Systeme (Apache Hive, Apache Storm, Apache Impala, Apache Giraph, Apache Drill, Apache Mahout usw.) mit jeweils eigenen APIs und Clusterkonfigurationen, was die Komplexität von Hadoop und die steile Lernkurve für Entwickler weiter erhöhte.

Die Frage war also (unter Berücksichtigung von Alan Kays Sprichwort "Einfache Dinge sollten einfach sein, komplexe Dinge sollten möglich sein"), ob es einen Weg gab, Hadoop und MR einfacher und schneller zu machen?

Sparks frühe Jahre im AMPLab

Forscher an der UC Berkeley, die zuvor an Hadoop MapReduce gearbeitet hatten, stellten sich dieser Herausforderung mit einem Projekt, das sie Spark nannten. Sie erkannten, dass MR für interaktive oder iterative Rechenaufgaben ineffizient (oder unlösbar) und ein komplexes Framework ist, das es zu erlernen gilt. Dieses Vorhaben begann 2009 im RAD Lab, das später zum AMPLab wurde (und heute als RISELab bekannt ist).

Frühe Veröffentlichungen über Spark zeigten, dass es bei bestimmten Aufträgen 10 bis 20 Mal schneller war als Hadoop MapReduce. Heute ist es um viele Größenordnungen schneller. Der zentrale Ansatz des Spark-Projekts bestand darin, Ideen aus Hadoop MapReduce zu übernehmen und das System zu verbessern: Es sollte hochgradig fehlertolerant und peinlich parallel sein, eine In-Memory-Speicherung für Zwischenergebnisse zwischen iterativen und interaktiven Map- und Reduce-Berechnungen unterstützen, einfache und kompatible APIs in mehreren Sprachen als Programmiermodell anbieten und andere Arbeitslasten auf einheitliche Weise unterstützen. Wir werden in Kürze auf diese Idee der Vereinheitlichung zurückkommen, denn sie ist ein wichtiges Thema bei Spark.

2013 war Spark weit verbreitet und einige der ursprünglichen Erfinder und Forscher - Matei Zaharia, Ali Ghodsi, Reynold Xin, Patrick Wendell, Ion Stoica und Andy Konwinski - übergaben das Spark-Projekt an die ASF und gründeten ein Unternehmen namens Databricks.

Databricks und die Community der Open-Source-Entwickler arbeiteten daran, Apache Spark 1.0 im Mai 2014 unter der Leitung der ASF zu veröffentlichen. Diese erste große Veröffentlichung gab den Anstoß für häufige zukünftige Veröffentlichungen und Beiträge von Databricks und über 100 kommerziellen Anbietern zu Apache Spark.

Was ist Apache Spark?

Apache Spark ist eine einheitliche Engine, die für die verteilte Datenverarbeitung in großem Maßstab entwickelt wurde, sei es in Rechenzentren oder in der Cloud.

Spark bietet In-Memory-Speicherung für Zwischenberechnungen und ist damit viel schneller als Hadoop MapReduce. Es enthält Bibliotheken mit kompatiblen APIs für maschinelles Lernen (MLlib), SQL für interaktive Abfragen (Spark SQL), Stream Processing (Structured Streaming) für die Interaktion mit Echtzeitdaten und Graph Processing (GraphX).

Die Designphilosophie von Spark basiert auf vier Hauptmerkmalen:

  • Geschwindigkeit

  • Einfachheit der Nutzung

  • Modularität

  • Erweiterbarkeit

Schauen wir uns an, was das für den Rahmen bedeutet.

Geschwindigkeit

Spark hat das Ziel der Geschwindigkeit auf verschiedene Weise verfolgt. Erstens profitiert die interne Implementierung immens von den jüngsten enormen Fortschritten der Hardware-Industrie bei der Verbesserung von Preis und Leistung von CPUs und Speicher. Heutige Commodity-Server sind billig, verfügen über Hunderte von Gigabyte Speicher, mehrere Kerne und das zugrunde liegende Unix-basierte Betriebssystem, das effizientes Multithreading und Parallelverarbeitung nutzt. Das Framework ist optimiert, um all diese Faktoren zu nutzen.

Zweitens baut Spark seine Abfrageberechnungen als gerichteten azyklischen Graphen (DAG) auf. Das Zeitplannungsprogramm und der Abfrageoptimierer konstruieren einen effizienten Berechnungsgraphen, der in der Regel in Aufgaben zerlegt werden kann, die von den Arbeitern im Cluster parallel ausgeführt werden. Und drittens nutzt die physische Ausführungs-Engine Tungsten die Whole-Stage-Code-Generierung, um kompakten Code für die Ausführung zu erzeugen (wir werden die SQL-Optimierung und die Whole-Stage-Code-Generierung in Kapitel 3 behandeln).

Da alle Zwischenergebnisse im Arbeitsspeicher gehalten werden und die E/A auf der Festplatte begrenzt ist, ergibt sich ein enormer Leistungsschub.

Benutzerfreundlichkeit

Spark erreicht Einfachheit, indem es eine grundlegende Abstraktion einer einfachen logischen Datenstruktur bereitstellt, die als Resilient Distributed Dataset (RDD) bezeichnet wird und auf der alle anderen strukturierten Datenabstraktionen auf höherer Ebene, wie z. B. DataFrames und Datasets, aufgebaut sind. Durch die Bereitstellung einer Reihe von Transformationen und Aktionen als Operationen bietet Spark ein einfaches Programmiermodell, mit dem du Big-Data-Anwendungen in vertrauten Sprachen erstellen kannst.

Modularität

Spark-Operationen können auf viele Arten von Workloads angewendet und in jeder der unterstützten Programmiersprachen ausgedrückt werden: Scala, Java, Python, SQL und R. Spark bietet einheitliche Bibliotheken mit gut dokumentierten APIs, die die folgenden Module als Kernkomponenten enthalten: Spark SQL, Spark Structured Streaming, Spark MLlib und GraphX, die alle Arbeitslasten unter einer Engine vereinen. Im nächsten Abschnitt werden wir uns all diese Module genauer ansehen.

Du kannst eine einzige Spark-Anwendung schreiben, die alles kann - du brauchst keine verschiedenen Engines für unterschiedliche Arbeitslasten und musst keine separaten APIs lernen. Mit Spark erhältst du eine einheitliche Verarbeitungs-Engine für deine Arbeitslasten.

Erweiterbarkeit

Spark konzentriert sich auf seine schnelle, parallele Berechnungsmaschine und nicht auf die Speicherung. Im Gegensatz zu Apache Hadoop, das sowohl die Speicherung als auch die Berechnung beinhaltete, entkoppelt Spark diese beiden Bereiche. Das bedeutet, dass du Spark verwenden kannst, um Daten aus unzähligen Quellen - Apache Hadoop, Apache Cassandra, Apache HBase, MongoDB, Apache Hive, RDBMSs und mehr - zu lesen und im Speicher zu verarbeiten. Sparks DataFrameReaderund DataFrameWriterkönnen auch erweitert werden, um Daten aus anderen Quellen wie Apache Kafka, Kinesis, Azure Storage und Amazon S3 in seine logische Datenabstraktion einzulesen, auf der es arbeiten kann.

Die Gemeinschaft der Spark-Entwickler pflegt eine Liste von Spark-Paketen von Drittanbietern als Teil des wachsenden Ökosystems (siehe Abbildung 1-2). Dieses reichhaltige Ökosystem an Paketen umfasst Spark-Konnektoren für eine Vielzahl externer Datenquellen, Leistungsüberwachungen und mehr.

Apache Spark’s ecosystem of connectors
Abbildung 1-2. Das Ökosystem der Konnektoren von Apache Spark

Unified Analytics

Der Gedanke der Vereinheitlichung ist zwar nicht einzigartig für Spark, aber er ist ein zentraler Bestandteil seiner Designphilosophie und Entwicklung. Im November 2016 würdigte die Association for Computing Machinery (ACM) Apache Spark und verlieh seinen Schöpfern den prestigeträchtigen ACM Award für ihr Papier, das Apache Spark als "Unified Engine for Big Data Processing" beschreibt. In dem preisgekrönten Papier heißt es, dass Spark alle separaten Stapelverarbeitungs-, Graphen-, Stream- und Abfrage-Engines wie Storm, Impala, Dremel, Pregel usw. durch einen vereinheitlichten Stapel von Komponenten ersetzt, der verschiedene Arbeitslasten unter einer einzigen verteilten, schnellen Engine abdeckt.

Apache Spark Komponenten als einheitlicher Stack

Wie in Abbildung 1-3 dargestellt, bietet Spark vier verschiedene Komponenten als Bibliotheken für unterschiedliche Arbeitslasten : Spark SQL, Spark MLlib, Spark Structured Streaming und GraphX. Jede dieser Komponenten ist von der fehlertoleranten Kern-Engine von Spark getrennt. Du schreibst deine Spark-Anwendung mithilfe von APIs und Spark wandelt sie in ein DAG um, das von der Kern-Engine ausgeführt wird. Unabhängig davon, ob du deinen Spark-Code mithilfe der bereitgestellten strukturierten APIs (die wir in Kapitel 3 behandeln) in Java, R, Scala, SQL oder Python schreibst, wird der zugrunde liegende Code in hochkompakten Bytecode zerlegt, der in den JVMs der Worker im Cluster ausgeführt wird.

Apache Spark components and API stack
Abbildung 1-3. Apache Spark-Komponenten und API-Stack

Schauen wir uns jede dieser Komponenten genauer an.

Spark SQL

Dieses Modul funktioniert gut mit strukturierten Daten. Du kannst Daten lesen, die in einer RDBMS-Tabelle oder aus Dateiformaten mit strukturierten Daten (CSV, Text, JSON, Avro, ORC, Parquet usw.) gespeichert sind, und dann permanente oder temporäre Tabellen in Spark erstellen. Wenn du die strukturierten APIs von Spark in Java, Python, Scala oder R verwendest, kannst du außerdem SQL-ähnliche Abfragen kombinieren, um die gerade in einen Spark-Datenrahmen eingelesenen Daten abzufragen. Bislang ist Spark SQL ANSI SQL:2003-konform und funktioniert auch als reine SQL-Engine.

In diesem Scala-Codeausschnitt kannst du zum Beispiel eine JSON-Datei auslesen, die auf Amazon S3 gespeichert ist, eine temporäre Tabelle erstellen und eine SQL-ähnliche Abfrage auf die Ergebnisse stellen, die als Spark DataFrame in den Speicher eingelesen werden:

// In Scala
// Read data off Amazon S3 bucket into a Spark DataFrame
spark.read.json("s3://apache_spark/data/committers.json")
  .createOrReplaceTempView("committers")
// Issue a SQL query and return the result as a Spark DataFrame
val results = spark.sql("""SELECT name, org, module, release, num_commits
    FROM committers WHERE module = 'mllib' AND num_commits > 10
    ORDER BY num_commits DESC""")

Du kannst ähnliche Codeschnipsel in Python, R oder Java schreiben und der generierte Bytecode wird identisch sein, was zu der gleichen Leistung führt.

Spark MLlib

Spark wird mit einer Bibliothek geliefert, die gängige Algorithmen für maschinelles Lernen (ML) enthält: MLlib. Seit der ersten Veröffentlichung von Spark hat sich die Leistung dieser Bibliothekskomponente durch die Verbesserungen der Spark 2.x zugrunde liegenden Engine erheblich verbessert. MLlib bietet viele gängige Algorithmen für maschinelles Lernen, die auf High-Level-APIs im Datenrahmen basieren, um Modelle zu erstellen.

Hinweis

Seit Apache Spark 1.6 ist das MLlib-Projekt in zwei Pakete aufgeteilt: spark.mllib und spark.ml. Letzteres ist die DataFrame-basierte API, während ersteres die RDD-basierten APIs enthält, die sich jetzt im Wartungsmodus befinden. Alle neuen Funktionen fließen in spark.ml ein. In diesem Buch wird "MLlib" als die Dachbibliothek für maschinelles Lernen in Apache Spark bezeichnet.

Diese APIs ermöglichen es dir, Merkmale zu extrahieren oder umzuwandeln, Pipelines (zum Trainieren und Auswerten) zu erstellen und Modelle während des Einsatzes beizubehalten (um sie zu speichern und neu zu laden). Weitere Hilfsmittel sind die Verwendung gängiger linearer Algebra-Operationen und Statistiken. MLlib enthält weitere Low-Level-ML-Primitive, darunter eine generische Gradientenabstiegsoptimierung. Der folgende Python-Codeausschnitt zeigt die grundlegenden Operationen, die ein Datenwissenschaftler bei der Erstellung eines Modells durchführen kann (ausführlichere Beispiele werden in den Kapiteln 10 und 11 behandelt):

# In Python
from pyspark.ml.classification import LogisticRegression
...
training = spark.read.csv("s3://...")
test = spark.read.csv("s3://...")

# Load training data
lr = LogisticRegression(maxIter=10, regParam=0.3, elasticNetParam=0.8)

# Fit the model
lrModel = lr.fit(training)

# Predict
lrModel.transform(test)
...

Spark Strukturiertes Streaming

Mit Apache Spark 2.0 wurden ein experimentelles Continuous-Streaming-Modell und Structured-Streaming-APIs eingeführt, die auf der Spark-SQL-Engine und den DataFrame-basierten APIs aufbauen. Mit Spark 2.2 war Structured Streaming allgemein verfügbar, so dass Entwickler es in ihren Produktionsumgebungen nutzen konnten.

Damit Big-Data-Entwickler sowohl statische Daten als auch Streaming-Daten von Engines wie Apache Kafka und anderen Streaming-Quellen kombinieren und in Echtzeit darauf reagieren können, betrachtet das neue Modell einen Stream als eine kontinuierlich wachsende Tabelle, an deren Ende neue Datenzeilen angehängt werden. Entwickler können diese Tabelle wie eine strukturierte Tabelle behandeln und Abfragen wie bei einer statischen Tabelle durchführen.

Im Rahmen des Structured-Streaming-Modells kümmert sich die Spark-SQL-Core-Engine um alle Aspekte der Fehlertoleranz und der Semantik für späte Daten, so dass sich die Entwickler relativ einfach auf das Schreiben von Streaming-Anwendungen konzentrieren können. Dieses neue Modell ersetzte das alte DStreams-Modell der Spark 1.x-Serie, auf das wir in Kapitel 8 näher eingehen werden. Außerdem wurde mit Spark 2.x und Spark 3.0 die Palette der Streaming-Datenquellen um Apache Kafka, Kinesis und HDFS-basierte oder Cloud-Speicherung erweitert.

Der folgende Codeschnipsel zeigt den typischen Aufbau einer Structured Streaming-Anwendung. Sie liest von einem Localhost-Socket und schreibt die Ergebnisse der Wortzählung in ein Apache Kafka-Topic:

# In Python
# Read a stream from a local host
from pyspark.sql.functions import explode, split
lines = (spark 
  .readStream
  .format("socket")
  .option("host", "localhost")
  .option("port", 9999)
  .load())

# Perform transformation
# Split the lines into words
words = lines.select(explode(split(lines.value, " ")).alias("word"))

# Generate running word count
word_counts = words.groupBy("word").count()

# Write out to the stream to Kafka
query = (word_counts
  .writeStream 
  .format("kafka") 
  .option("topic", "output"))

GraphX

Wie der Name schon sagt, ist GraphX eine Bibliothek zur Bearbeitung von Graphen (z. B. soziale Netzwerkgraphen, Routen und Verbindungspunkte oder Netzwerktopologiegraphen) und zur Durchführung von graphparallelen Berechnungen. Sie bietet die Standardgraphenalgorithmen für Analysen, Verbindungen und Traversierungen, die von Nutzern aus der Community beigesteuert wurden: Zu den verfügbaren Algorithmen gehören PageRank, Connected Components und Triangle Counting.1

Dieses Codeschnipsel zeigt ein einfaches Beispiel dafür, wie man zwei Graphen mit den GraphX APIs verbindet:

// In Scala
val graph = Graph(vertices, edges)
messages = spark.textFile("hdfs://...")
val graph2 = graph.joinVertices(messages) {
  (id, vertex, msg) => ...
}

Die verteilte Ausführung von Apache Spark

Wenn du bis hierher gelesen hast, weißt du bereits, dass Spark eine verteilte Datenverarbeitungsmaschine ist, deren Komponenten auf einem Cluster von Rechnern zusammenarbeiten. Bevor wir uns in den folgenden Kapiteln dieses Buches mit der Programmierung mit Spark beschäftigen, musst du verstehen, wie alle Komponenten der verteilten Architektur von Spark zusammenarbeiten und miteinander kommunizieren und welche Bereitstellungsmodi es gibt.

Schauen wir uns zunächst die einzelnen Komponenten in Abbildung 1-4 an und wie sie sich in die Architektur einfügen. Auf einer hohen Ebene der Spark-Architektur besteht eine Spark-Anwendung aus einem Treiberprogramm, das für die Orchestrierung paralleler Operationen auf dem Spark-Cluster verantwortlich ist. Der Treiber greift auf die verteilten Komponenten im Cluster - die Spark-Executors und den Clustermanager - über eine SparkSession zu.

Apache Spark components and architecture
Abbildung 1-4. Komponenten und Architektur von Apache Spark

Funken-Treiber

Als der Teil der Spark-Anwendung, der für die Instanziierung einer SparkSession verantwortlich ist, hat der Spark-Treiber mehrere Aufgaben: Er kommuniziert mit dem Clustermanager; er fordert vom Clustermanager Ressourcen (CPU, Speicher usw.) für die Executors (JVMs) von Spark an; und er wandelt alle Spark-Operationen in DAG-Berechnungen um, plant sie und verteilt ihre Ausführung als Aufgaben auf die Spark-Executors. Sobald die Ressourcen zugewiesen sind, kommuniziert sie direkt mit den Executors.

SparkSession

In Spark 2.0 wurde SparkSession zu einem einheitlichen Zugang zu allen Spark-Operationen und -Daten. Es fasst nicht nur frühere Einstiegspunkte zu Spark wie SparkContext, SQLContext, HiveContext, SparkConf und StreamingContext zusammen, sondern macht auch die Arbeit mit Spark einfacher und leichter.

Hinweis

Obwohl in Spark 2.x der SparkSession alle anderen Kontexte zusammenfasst, kannst du immer noch auf die einzelnen Kontexte und ihre jeweiligen Methoden zugreifen. Auf diese Weise hat die Community die Abwärtskompatibilität gewahrt. Das heißt, dein alter 1.x-Code mit SparkContext oder SQLContext wird weiterhin funktionieren.

Über diesen einen Conduit kannst du JVM-Laufzeitparameter erstellen, Datenrahmen und Datensätze definieren, aus Datenquellen lesen, auf Katalog-Metadaten zugreifen und Spark-SQL-Abfragen stellen. SparkSession bietet einen einheitlichen Einstiegspunkt für alle Spark-Funktionen.

In einer eigenständigen Spark-Anwendung kannst du eine SparkSession mithilfe einer der High-Level-APIs in der Programmiersprache deiner Wahl erstellen. In der Spark-Shell (mehr dazu im nächsten Kapitel) wird die SparkSession für dich erstellt, und du kannst über eine globale Variable namens spark oder sc darauf zugreifen.

Während du in Spark 1.x einzelne Kontexte (für Streaming, SQL usw.) erstellen musstest, was zusätzlichen Boilerplate-Code mit sich brachte, kannst du in einer Spark 2.x-Anwendung ein SparkSession pro JVM erstellen und es für eine Reihe von Spark-Operationen verwenden.

Schauen wir uns ein Beispiel an:

// In Scala
import org.apache.spark.sql.SparkSession

// Build SparkSession
val spark = SparkSession
  .builder
  .appName("LearnSpark")
  .config("spark.sql.shuffle.partitions", 6)
  .getOrCreate()
...
// Use the session to read JSON 
val people = spark.read.json("...")
...
// Use the session to issue a SQL query
val resultsDF = spark.sql("SELECT city, pop, state, zip FROM table_name")

Cluster Manager

Der Clustermanager ist für die Verwaltung und Zuweisung von Ressourcen für den Cluster von Knoten zuständig, auf dem deine Spark-Anwendung läuft. Derzeit unterstützt Spark vier Clustermanager: den integrierten eigenständigen Clustermanager, Apache Hadoop YARN, Apache Mesos und Kubernetes.

Spark-Vollstrecker

Auf jedem Worker-Knoten im Cluster läuft ein Spark-Executor. Die Executors kommunizieren mit dem Treiberprogramm und sind für die Ausführung von Aufgaben auf den Workern verantwortlich. In den meisten Bereitstellungsmodi läuft nur ein einziger Executor pro Knoten.

Bereitstellungsmodi

Ein attraktives Merkmal von Spark ist die Unterstützung zahlreicher Bereitstellungsmodi, die es Spark ermöglichen, in verschiedenen Konfigurationen und Umgebungen zu laufen. Da der Clustermanager unabhängig davon ist, wo er ausgeführt wird (solange er die Executors von Spark verwalten und Ressourcenanforderungen erfüllen kann ), kann Spark in einigen der beliebtesten Umgebungen - wie Apache Hadoop YARN und Kubernetes - eingesetzt werden und in verschiedenen Modi arbeiten. Tabelle 1-1 fasst die verfügbaren Bereitstellungsmodi zusammen.

Tabelle 1-1. Spickzettel für Spark-Einsatzmodi
Modus Funken-Treiber Spark-Vollstrecker Cluster Manager
Lokales Läuft auf einer einzelnen JVM, z. B. auf einem Laptop oder einem einzelnen Knoten Läuft auf der gleichen JVM wie der Treiber Läuft auf demselben Host
Eigenständig Kann auf jedem Knoten im Cluster laufen Jeder Knoten im Cluster startet seine eigene Executor-JVM Kann jedem beliebigen Host im Cluster zugewiesen werden
YARN (Kunde) Läuft auf einem Client, der nicht Teil des Clusters ist Der Container des NodeManagers von YARN Der Ressourcenmanager von YARN arbeitet mit dem Application Master von YARN zusammen, um die Container auf den NodeManagern für die Executors zuzuweisen.
YARN (Cluster) Läuft mit dem YARN Application Master Dasselbe wie im YARN-Client-Modus Dasselbe wie im YARN-Client-Modus
Kubernetes Läuft in einem Kubernetes Pod Jeder Arbeiter läuft in seinem eigenen Pod Kubernetes Meister

Verteilte Daten und Partitionen

Die eigentlichen physischen Daten werden in Form von Partitionen auf die Speicherung im HDFS oder in der Cloud verteilt (siehe Abbildung 1-5). Während die Daten als Partitionen über den physischen Cluster verteilt sind, behandelt Spark jede Partition als logische Datenabstraktion auf hoher Ebene - als Datenrahmen im Speicher. Obwohl dies nicht immer möglich ist, wird jedem Spark-Executor vorzugsweise eine Aufgabe zugewiesen, bei der er die Partition lesen muss, die ihm im Netzwerk am nächsten liegt, um die Datenlokalität zu beachten.

Data is distributed across physical machines
Abbildung 1-5. Daten werden auf physische Maschinen verteilt

Die Partitionierung ermöglicht eine effiziente Parallelisierung. Ein verteiltes Schema, bei dem die Daten in Brocken oder Partitionen aufgeteilt werden, ermöglicht es den Spark-Executors, nur die Daten zu verarbeiten, die sich in ihrer Nähe befinden, um die Netzwerkbandbreite zu minimieren. Das bedeutet, dass jedem Executor-Kern eine eigene Datenpartition zur Bearbeitung zugewiesen wird (siehe Abbildung 1-6).

Each executor’s core gets a partition of data to work on
Abbildung 1-6. Jeder Executor-Kern erhält eine Partition von Daten zur Bearbeitung

In diesem Codeschnipsel werden die physischen Daten, die in den Clustern gespeichert sind, beispielsweise in acht Partitionen aufgeteilt, und jeder Executor erhält eine oder mehrere Partitionen, die er in seinen Speicher einlesen kann:

# In Python
log_df = spark.read.text("path_to_large_text_file").repartition(8)
print(log_df.rdd.getNumPartitions())

Mit diesem Code wird ein Datenrahmen mit 10.000 Ganzzahlen erstellt, die auf acht Partitionen im Speicher verteilt sind:

# In Python
df = spark.range(0, 10000, 1, 8)
print(df.rdd.getNumPartitions())

Beide Codeschnipsel werden 8 ausdrucken.

In den Kapiteln 3 und 7 werden wir besprechen, wie du die Partitionierungskonfiguration für maximale Parallelität abstimmst und änderst, je nachdem, wie viele Kerne du in deinen Executors hast.

Die Erfahrung des Entwicklers

Für Entwickler gibt es nichts Attraktiveres als eine Reihe von kompatiblen APIs, die die Produktivität steigern und einfach, intuitiv und aussagekräftig zu bedienen sind. Apache Spark ist für Entwickler vor allem wegen seiner benutzerfreundlichen APIs für kleine bis große Datensätze in verschiedenen Sprachen attraktiv: Scala, Java, Python, SQL und R.

Eine der Hauptmotivationen für Spark 2.x war es, das Framework zu vereinheitlichen und zu vereinfachen, indem die Anzahl der Konzepte, mit denen sich die Entwickler auseinandersetzen müssen, begrenzt wurde. Mit Spark 2.x wurden Abstraktions-APIs auf höherer Ebene als domänenspezifische Sprachkonstrukte eingeführt, die die Programmierung von Spark sehr ausdrucksstark und für die Entwickler angenehm machen. Du drückst aus, was die Aufgabe oder Operation berechnen soll, nicht wie sie berechnet werden soll, und überlässt es Spark, herauszufinden, wie es am besten für dich funktioniert. Wir werden diese strukturierten APIs in Kapitel 3 behandeln, aber zuerst wollen wir uns ansehen, wer die Spark-Entwickler sind.

Wer nutzt Spark, und wofür?

Es überrascht nicht, dass die meisten Entwickler, die sich mit Big Data befassen, Dateningenieure, Datenwissenschaftler oder Ingenieure für maschinelles Lernen sind. Sie fühlen sich zu Spark hingezogen, weil sie mit einer einzigen Engine und vertrauten Programmiersprachen eine Reihe von Anwendungen erstellen können.

Natürlich können Entwickler/innen viele Hüte tragen und manchmal sowohl Data Science- als auch Data Engineering-Aufgaben übernehmen, vor allem in Startup-Unternehmen oder kleineren Entwicklungsgruppen. Die Grundlage für all diese Aufgaben sind jedoch Daten - riesige Datenmengen - die Grundlage.

Datenwissenschaftliche Aufgaben

Als Disziplin, die im Zeitalter von Big Data an Bedeutung gewonnen hat, geht es in der Datenwissenschaft darum, aus Daten Geschichten zu erzählen. Doch bevor sie diese Geschichten erzählen können, müssen Datenwissenschaftler/innen die Daten bereinigen, sie untersuchen, um Muster zu entdecken, und Modelle erstellen, um Ergebnisse vorherzusagen oder zu empfehlen. Einige dieser Aufgaben erfordern Kenntnisse in Statistik, Mathematik, Informatik und Programmierung.

Die meisten Datenwissenschaftler/innen sind geübt im Umgang mit Analysetools wie SQL, kennen sich mit Bibliotheken wie NumPy und Pandas aus und sind mit Programmiersprachen wie R und Python vertraut. Aber sie müssen auch wissen, wie man Daten verarbeitet oder umwandelt und wie man etablierte Klassifizierungs-, Regressions- oder Clustering-Algorithmen zur Erstellung von Modellen verwendet. Oft sind ihre Aufgaben iterativ, interaktiv, ad hoc oder experimentell, um ihre Hypothesen zu bestätigen.

Zum Glück unterstützt Spark diese verschiedenen Tools. Die MLlib von Spark bietet einen gemeinsamen Satz von Algorithmen für maschinelles Lernen zum Aufbau von Modellpipelines, die High-Level-Schätzer, Transformatoren und Data Featurizer verwenden. Spark SQL und die Spark Shell erleichtern die interaktive und Ad-hoc-Exploration von Daten.

Außerdem können Datenwissenschaftler mit Spark große Datensätze bearbeiten und das Training und die Auswertung ihrer Modelle skalieren. Mit Apache Spark 2.4 wurde im Rahmen des Projekts Hydrogen ein neues Zeitplannungsprogramm eingeführt, das die fehlertoleranten Anforderungen an das Training und die Zeitplanung von Deep-Learning-Modellen in einer verteilten Umgebung erfüllt, und mit Spark 3.0 wurde die Möglichkeit eingeführt, die Sammlung von GPU-Ressourcen in den Bereitstellungsmodi Standalone, YARN und Kubernetes zu unterstützen. Das bedeutet, dass Entwickler, deren Aufgaben Deep-Learning-Techniken erfordern, Spark nutzen können.

Datentechnische Aufgaben

Nachdem sie ihre Modelle erstellt haben, müssen Datenwissenschaftler/innen oft mit anderen Teammitgliedern zusammenarbeiten, die für den Einsatz der Modelle verantwortlich sein können. Oder sie müssen eng mit anderen zusammenarbeiten, um rohe, schmutzige Daten zu erstellen und in saubere Daten umzuwandeln, die von anderen Datenwissenschaftlern leicht konsumiert oder genutzt werden können. Ein Klassifizierungs- oder Clustermodell zum Beispiel existiert nicht isoliert, sondern arbeitet mit anderen Komponenten wie einer Webanwendung oder einer Streaming-Engine wie Apache Kafka oder als Teil einer größeren Datenpipeline zusammen. Diese Pipeline wird oft von Dateningenieuren erstellt.

Data Engineers verfügen über ein ausgeprägtes Verständnis der Prinzipien und Methoden der Softwareentwicklung und besitzen die Fähigkeit, skalierbare Datenpipelines für einen bestimmten Geschäftszweck zu erstellen. Datenpipelines ermöglichen eine durchgängige Umwandlung von Rohdaten, die aus unzähligen Quellen stammen. Die Daten werden bereinigt, damit sie von Entwicklern weiterverarbeitet, in der Cloud oder in NoSQL- oder RDBMS-Systemen für die Erstellung von Berichten gespeichert oder Datenanalysten über Business Intelligence-Tools zugänglich gemacht werden können.

Mit Spark 2.x wurde ein evolutionäres Streaming-Modell eingeführt, das als kontinuierliche Anwendungen mit Structured Streaming bezeichnet wird (wird in Kapitel 8 ausführlich behandelt). Mit den Structured Streaming APIs können Dateningenieure komplexe Datenpipelines aufbauen, die es ihnen ermöglichen, Daten sowohl aus Echtzeit- als auch aus statischen Datenquellen per ETL zu verarbeiten.

Dateningenieure nutzen Spark, weil es eine einfache Möglichkeit zur Parallelisierung von Berechnungen bietet und die gesamte Komplexität der Verteilung und Fehlertoleranz ausblendet. So können sie sich auf die Verwendung von Datenrahmen-basierten APIs und Abfragen in domänenspezifischen Sprachen (DSLs) konzentrieren, um ETL durchzuführen und Daten aus verschiedenen Quellen zu lesen und zu kombinieren.

Die Leistungsverbesserungen in Spark 2.x und Spark 3.0, die auf den Catalyst-Optimierer für SQL und Tungsten für kompakte Codegenerierung zurückzuführen sind, haben das Leben für Dateningenieure viel einfacher gemacht. Sie können eine der drei Spark-APIs - RDDs, Datenrahmen oder Datensätze - verwenden, die für die jeweilige Aufgabe geeignet ist, und die Vorteile von Spark nutzen.

Annahme und Ausweitung in der Gemeinschaft

Es überrascht nicht, dass Apache Spark in der Open-Source-Gemeinde, vor allem bei Dateningenieuren und -wissenschaftlern, großen Anklang gefunden hat. Seine Design-Philosophie und die Aufnahme in die Apache Software Foundation haben in der Entwicklergemeinschaft großes Interesse geweckt.

Heute gibt es weltweit über 600 Apache Spark Meetup-Gruppen mit fast einer halben Million Mitgliedern. Jede Woche hält jemand auf der Welt einen Vortrag auf einem Meetup oder einer Konferenz oder veröffentlicht einen Blogbeitrag darüber, wie man Spark zum Aufbau von Datenpipelines nutzt. Der Spark + AI Summit ist die größte Konferenz, die sich mit der Nutzung von Spark für maschinelles Lernen, Data Engineering und Data Science in vielen Branchen beschäftigt.

Seit der ersten Version 1.0 von Spark im Jahr 2014 hat es viele kleinere und größere Versionen gegeben, wobei die letzte größere Version Spark 3.0 im Jahr 2020 erscheint. Dieses Buch behandelt Aspekte von Spark 2.x und Spark 3.0. Zum Zeitpunkt der Veröffentlichung des Buches wird die Community Spark 3.0 veröffentlicht haben, und der meiste Code in diesem Buch wurde mit Spark 3.0-preview2 getestet.

Im Laufe seiner Veröffentlichungen hat Spark immer wieder Mitwirkende aus der ganzen Welt und aus zahlreichen Organisationen angezogen. Heute hat Spark fast 1.500 Mitwirkende, weit über 100 Veröffentlichungen, 21.000 Forks und etwa 27.000 Commits auf GitHub, wie Abbildung 1-7 zeigt. Wir hoffen, dass du dich nach der Lektüre dieses Buches dazu berufen fühlst, ebenfalls einen Beitrag zu leisten.

The state of Apache Spark on GitHub (source: https://github.com/apache/spark)
Abbildung 1-7. Der Stand von Apache Spark auf GitHub (Quelle: https://github.com/apache/spark)

Jetzt können wir uns dem Spaß am Lernen zuwenden - wo und wie du mit Spark anfangen kannst. Im nächsten Kapitel zeigen wir dir, wie du in drei einfachen Schritten mit Spark loslegen kannst .

1 GraphFrames wurde von Databricks als Open-Source-Projekt der Community zur Verfügung gestellt und ist eine allgemeine Bibliothek zur Graphenverarbeitung, die dem GraphX von Apache Spark ähnelt, aber DataFrame-basierte APIs verwendet.

Get Spark lernen, 2. Auflage now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.