Kapitel 1. Streaming 101
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
Die Verarbeitung von Streaming-Daten ist heutzutage eine große Sache im Bereich Big Data, und das aus guten Gründen; unter anderem :
-
Unternehmen wünschen sich immer zeitnähere Einblicke in ihre Daten, und die Umstellung auf Streaming ist ein guter Weg, um die Latenzzeit zu verringern
-
Die riesigen, unbegrenzten Datenmengen, die in modernen Unternehmen immer häufiger vorkommen, lassen sich mit einem System, das für solche unendlichen Datenmengen ausgelegt ist, leichter bewältigen.
-
Durch die Verarbeitung der Daten bei ihrem Eintreffen werden die Arbeitslasten gleichmäßiger über die Zeit verteilt, was zu einer gleichmäßigeren und vorhersehbaren Nutzung der Ressourcen führt.
Trotz des großen Interesses der Wirtschaft am Streaming blieben Streaming-Systeme im Vergleich zu ihren Batch-Brüdern lange Zeit relativ unreif. Erst in letzter Zeit hat sich das Blatt endgültig in die andere Richtung gewendet. In meinen aufbrausenden Momenten hoffe ich, dass dies zu einem kleinen Teil auf die kräftige Portion Aufputschmittel zurückzuführen ist, die ich in meinen Blogbeiträgen "Streaming 101" und "Streaming 102" (auf denen die ersten Kapitel dieses Buches ganz offensichtlich basieren) gegeben habe. In Wirklichkeit gibt es aber auch ein großes Interesse der Branche an der Entwicklung von Streaming-Systemen und eine Menge kluger und aktiver Leute, die Spaß daran haben, sie zu entwickeln.
Auch wenn die Schlacht um die allgemeine Befürwortung von Streaming meiner Meinung nach eigentlich schon gewonnen ist, werde ich meine ursprünglichen Argumente aus "Streaming 101" mehr oder weniger unverändert wiedergeben. Erstens sind sie auch heute noch gültig, auch wenn ein Großteil der Industrie den Schlachtruf inzwischen beherzigt. Und zweitens gibt es eine Menge Leute da draußen, die das Memo immer noch nicht bekommen haben; dieses Buch ist ein erweiterter Versuch, diese Punkte zu vermitteln.
Zu Beginn gehe ich auf einige wichtige Hintergrundinformationen ein, die den Rahmen für die restlichen Themen bilden, die ich besprechen möchte. Das tue ich in drei speziellen Abschnitten:
- Terminologie
-
Um präzise über komplexe Themen zu sprechen, müssen die Begriffe genau definiert werden. Bei einigen Begriffen, die im heutigen Sprachgebrauch überladen interpretiert werden, werde ich versuchen, genau festzulegen, was ich meine, wenn ich sie sage.
- Fähigkeiten
-
Ich weise auf die oft wahrgenommenen Mängel von Streaming-Systemen hin. Außerdem schlage ich eine Denkweise vor, die die Entwickler von Datenverarbeitungssystemen meiner Meinung nach annehmen müssen, um den Bedürfnissen moderner Datenkonsumenten in Zukunft gerecht zu werden.
- Zeitdomänen
-
Ich stelle die beiden Hauptbereiche der Zeit vor, die für die Datenverarbeitung relevant sind, zeige, wie sie zusammenhängen, und weise auf einige der Schwierigkeiten hin, die diese beiden Bereiche mit sich bringen.
Terminologie: Was ist Streaming?
Bevor wir weitermachen, möchte ich eine Sache aus dem Weg räumen: Was ist Streaming? Der Begriff "Streaming" wird heute für viele verschiedene Dinge verwendet (und der Einfachheit halber habe ich ihn bis jetzt eher locker verwendet), was zu Missverständnissen darüber führen kann, was Streaming wirklich ist oder was Streaming-Systeme tatsächlich können. Deshalb möchte ich den Begriff lieber etwas genauer definieren.
Der Kern des Problems besteht darin, dass viele Dinge, die eigentlich durch das beschrieben werden sollten , was sie sind (unbegrenzte Datenverarbeitung, ungefähre Ergebnisse usw.), umgangssprachlich durch die Art und Weise beschrieben werden , wie sie in der Vergangenheit erreicht wurden (d. h. durch Streaming-Ausführungsmaschinen). Dieser Mangel an Präzision in der Terminologie vernebelt, was Streaming wirklich bedeutet, und in einigen Fällen belastet es Streaming-Systeme selbst mit dem Eindruck, dass ihre Fähigkeiten auf Eigenschaften beschränkt sind, die historisch als "Streaming" beschrieben wurden, wie ungefähre oder spekulative Ergebnisse.
In Anbetracht der Tatsache, dass gut konzipierte Streaming-Systeme genauso (oder sogar besser) in der Lage sind, korrekte, konsistente und wiederholbare Ergebnisse zu erzielen, wie jede bestehende Batch-Engine, ziehe ich es vor, den Begriff "Streaming" auf eine ganz bestimmte Bedeutung zu beschränken:
- Streaming-System
-
Eine Art von Datenverarbeitungsmaschine, die für unendliche Datensätze konzipiert ist.1
Wenn ich über niedrige Latenzzeiten, ungefähre oder spekulative Ergebnisse sprechen möchte, verwende ich diese spezifischen Wörter, anstatt sie ungenau als "Streaming" zu bezeichnen.
Präzise Begriffe sind auch nützlich, wenn es um die verschiedenen Arten von Daten geht, auf die man treffen kann. Aus meiner Sicht gibt es zwei wichtige (und orthogonale) Dimensionen , die die Form eines bestimmten Datensatzes bestimmen: Kardinalität und Konstitution.
Die Kardinalität eines Datensatzes bestimmt seine Größe, wobei der wichtigste Aspekt der Kardinalität darin besteht, ob ein bestimmter Datensatz endlich oder unendlich ist. Hier sind die beiden Begriffe, die ich am liebsten verwende, um die grobe Kardinalität eines Datensatzes zu beschreiben:
- Begrenzte Daten
- Unbegrenzte Daten
-
Eine Art von Datensatz, der unendlich groß ist (zumindest theoretisch).
Die Kardinalität ist wichtig, weil die Unbegrenztheit unendlicher Datensätze die Datenverarbeitungssysteme, die sie nutzen, zusätzlich belastet. Mehr dazu im nächsten Abschnitt.
Die Beschaffenheit eines Datensatzes hingegen ( ) bestimmt seine physische Erscheinungsform. Die Verfassung legt also fest, wie man mit den betreffenden Daten interagieren kann. Wir werden uns erst in Kapitel 6 eingehend mit den Konstitutionen befassen, aber um dir einen kurzen Überblick zu verschaffen, sind vor allem zwei Konstitutionen von Bedeutung:
- Tabelle
-
Eine ganzheitliche Sicht auf einen Datensatz zu einem bestimmten Zeitpunkt. SQL-Systeme haben traditionell mit Tabellen gearbeitet.
- Stream2
-
Eine Element-für-Element-Ansicht der Entwicklung eines Datensatzes im Laufe der Zeit. Die MapReduce-Linie von Datenverarbeitungssystemen haben traditionell in Streams gearbeitet.
In den Kapiteln 6, 8 und 9 befassen wir uns eingehend mit der Beziehung zwischen Streams und Tabellen, und in Kapitel 8 lernen wir auch das zugrundeliegende Konzept der zeitvariablen Relationen kennen, das die beiden miteinander verbindet. Aber bis dahin beschäftigen wir uns hauptsächlich mit Streams, denn das ist die Konstitution, mit der Pipeline-Entwickler heute in den meisten Datenverarbeitungssystemen (sowohl Batch als auch Streaming) direkt interagieren. Es ist auch die Konstitution, die am ehesten die besonderen Herausforderungen der Stream-Verarbeitung verkörpert.
Über die stark übertriebenen Einschränkungen von Streaming
Lassen Sie uns nun ein wenig darüber sprechen, was Streaming-Systeme können und was nicht, wobei die Betonung auf können liegt. Eines der wichtigsten Dinge, die ich in diesem Kapitel vermitteln möchte, ist, wie leistungsfähig ein gut konzipiertes Streaming-System sein kann. Streaming-Systeme wurden in der Vergangenheit eher in eine Nische verbannt, in der sie niedrige Latenzzeiten, ungenaue oder spekulative Ergebnisse lieferten, oft in Verbindung mit einem leistungsfähigeren Batch-System , das letztendlich korrekte Ergebnisse lieferte; mit anderen Worten, die Lambda-Architektur.
Für diejenigen unter euch, die mit der Lambda-Architektur noch nicht vertraut sind: Die Grundidee ist, dass du ein Streaming-System neben einem Batch-System laufen lässt, die beide im Wesentlichen dieselbe Berechnung durchführen. Das Streaming-System liefert dir ungenaue Ergebnisse mit geringer Latenz (entweder weil ein Näherungsalgorithmus verwendet wird oder weil das Streaming-System selbst nicht korrekt ist), und einige Zeit später kommt ein Batch-System und liefert dir die korrekte Ausgabe. Ursprünglich wurde Lambda von Nathan Marz von Twitter (dem Erfinder von Storm) vorgeschlagen und war ziemlich erfolgreich, weil es für die damalige Zeit eine fantastische Idee war: Streaming-Engines waren ein wenig enttäuschend, was die Korrektheit anging, und Batch-Engines waren von Natur aus so unhandlich, wie man es erwarten würde. Leider ist die Wartung eines Lambda-Systems sehr mühsam: Du musst zwei unabhängige Versionen deiner Pipeline erstellen, bereitstellen und warten und die Ergebnisse der beiden Pipelines am Ende irgendwie zusammenführen.
Als jemand, der jahrelang an einer stark konsistenten Streaming-Engine gearbeitet hat, fand ich das gesamte Prinzip der Lambda-Architektur auch ein bisschen unappetitlich. Es überrascht daher nicht, dass ich ein großer Fan von Jay Kreps ' Beitrag "Questioning the Lambda Architecture" war, als er veröffentlicht wurde. Das war eine der ersten öffentlichkeitswirksamen Aussagen gegen die Notwendigkeit der Dual-Mode-Ausführung. Erfreulich. Kreps ging auf die Frage der Wiederholbarkeit im Zusammenhang mit der Verwendung eines wiederholbaren Systems wie Kafka als Streaming-Verbindung ein und schlug sogar die Kappa-Architektur vor, die im Grunde genommen bedeutet, dass eine einzige Pipeline mit einem gut konzipierten System ausgeführt wird, das für die jeweilige Aufgabe geeignet ist. Ich bin nicht davon überzeugt, dass dieses Konzept einen eigenen Namen mit griechischen Buchstaben braucht, aber im Prinzip unterstütze ich die Idee voll und ganz.
Ehrlich gesagt, würde ich noch einen Schritt weiter gehen. Ich würde argumentieren, dass gut konzipierte Streaming-Systeme eigentlich eine strenge Obermenge der Batch-Funktionalität bieten. Abgesehen von einem Effizienzdelta sollten Batch-Systeme, wie sie heute existieren, überflüssig sein. Ein großes Lob an die Leute von Apache Flink, die sich diese Idee zu Herzen genommen haben und ein System entwickelt haben, das selbst im Batch-Modus die ganze Zeit streamt - ich bin begeistert.
Die Konsequenz aus all dem ist, dass die breite Reifung von Streaming-Systemen in Kombination mit robusten Frameworks für unbegrenzte Datenverarbeitung es mit der Zeit ermöglichen wird, die Lambda-Architektur in die Antike der Big-Data-Geschichte zu verbannen, wo sie hingehört. Ich glaube, dass die Zeit gekommen ist, dies in die Tat umzusetzen. Denn um dies zu erreichen, d.h. um Batch mit seinen eigenen Waffen zu schlagen, brauchst du eigentlich nur zwei Dinge:
- Korrektheit
-
So erreichst du Gleichheit mit der Stapelverarbeitung. Im Kern läuft die Korrektheit auf eine konsistente Speicherung hinaus. Streaming-Systeme brauchen eine Methode zum Checkpointing des persistenten Zustands im Laufe der Zeit (darüber hat Kreps in seinem Beitrag "Why local state is a fundamental primitive in stream processing" gesprochen), und sie muss so gut konzipiert sein, dass sie auch bei Maschinenausfällen konsistent bleibt. Als Spark Streaming vor einigen Jahren zum ersten Mal in der öffentlichen Big Data-Szene auftauchte, war es ein Leuchtfeuer der Konsistenz in einer ansonsten dunklen Streaming-Welt. Zum Glück haben sich die Dinge seither erheblich verbessert, aber es ist bemerkenswert, wie viele Streaming-Systeme immer noch versuchen, ohne starke Konsistenz auszukommen.
Um es noch einmal zu wiederholen - denn dieser Punkt ist wichtig: Für die einmalige Verarbeitung ist eine starke Konsistenz erforderlich,3 Diese ist wiederum Voraussetzung für Korrektheit, die wiederum Voraussetzung für jedes System ist, das die Fähigkeiten von Batch-Systemen erreichen oder übertreffen will. Wenn dir deine Ergebnisse nicht wirklich egal sind, solltest du jedes Streaming-System meiden, das keinen konsistenten Zustand bietet. Bei Batch-Systemen musst du nicht im Voraus überprüfen, ob sie in der Lage sind, korrekte Antworten zu liefern; verschwende deine Zeit nicht mit Streaming-Systemen, die diese Anforderungen nicht erfüllen können.
Wenn du mehr darüber erfahren möchtest, was nötig ist, um eine starke Konsistenz in einem Streaming-System zu erreichen, empfehle ich dir die Papiere zu MillWheel, Spark Streaming und Flink Snapshotting. Alle drei nehmen sich viel Zeit, um über Konsistenz zu sprechen. Reuven geht in Kapitel 5 auf die Konsistenzgarantien ein, und wenn du noch mehr wissen willst, findest du in der Literatur und anderswo eine große Menge an hochwertigen Informationen zu diesem Thema.
- Werkzeuge für das Denken über Zeit
-
So kommst du über Batch hinaus. Gute Werkzeuge für zeitliche Schlussfolgerungen sind unerlässlich für den Umgang mit unbegrenzten, ungeordneten Daten mit unterschiedlichem Ereignis-Zeit-Verlauf. Immer mehr moderne Datensätze weisen diese Merkmale auf, und den bestehenden Batch-Systemen (und auch vielen Streaming-Systemen) fehlen die nötigen Werkzeuge, um mit den damit verbundenen Schwierigkeiten fertig zu werden (was sich allerdings gerade jetzt, während ich diese Zeilen schreibe, rapide ändert). Wir werden den größten Teil dieses Buches damit verbringen, verschiedene Aspekte dieses Punktes zu erklären und zu beleuchten.
Zu Beginn erhalten wir ein grundlegendes Verständnis des wichtigen Konzepts der Zeitdomänen. Danach werfen wir einen genaueren Blick darauf, was ich mit unbegrenzten, ungeordneten Daten mit unterschiedlicher Ereignis-Zeit-Schiefe meine. Der Rest des Kapitels befasst sich mit gängigen Ansätzen für die Verarbeitung begrenzter und unbegrenzter Daten, sowohl mit Batch- als auch mit Streaming-Systemen.
Ereigniszeit vs. Bearbeitungszeit
Um überzeugend über grenzenlose Datenverarbeitung sprechen zu können, ist ein klares Verständnis der beteiligten Zeitbereiche erforderlich. In jedem Datenverarbeitungssystem gibt es in der Regel zwei Zeitbereiche, um die wir uns kümmern:
- Zeit der Veranstaltung
-
Das ist der Zeitpunkt, an dem die Ereignisse tatsächlich eingetreten sind.
- Bearbeitungszeit
-
Das ist der Zeitpunkt, an dem Ereignisse im System beobachtet werden.
Nicht in allen Anwendungsfällen sind Ereigniszeiten wichtig (und wenn das bei dir nicht der Fall ist, ist dein Leben einfacher), aber in vielen schon. Beispiele sind die Charakterisierung des Nutzerverhaltens im Zeitverlauf, die meisten Abrechnungsanwendungen und viele Arten der Anomalieerkennung, um nur einige zu nennen.
In einer idealen Welt wären Ereignis- und Verarbeitungszeit immer gleich lang, da die Ereignisse sofort nach ihrem Auftreten verarbeitet würden. Die Realität sieht jedoch anders aus. Die Abweichung zwischen Ereignis- und Verarbeitungszeit ist nicht nur ungleich Null, sondern hängt oft auch von den Eigenschaften der zugrunde liegenden Eingabequellen, der Ausführungsmaschine und der Hardware ab. Zu den Faktoren, die sich auf die Höhe der Abweichung auswirken können, gehören die folgenden:
-
Gemeinsame Ressourcenbeschränkungen, wie Netzwerküberlastung, Netzwerkpartitionen oder gemeinsam genutzte CPUs in einer nicht dedizierten Umgebung
-
Software-Ursachen wie verteilte Systemlogik, Konkurrenzkampf usw.
-
Merkmale der Daten selbst, wie z. B. Schlüsselverteilung, Varianz im Durchsatz oder Varianz in der Unordnung (z. B. ein Flugzeug voller Menschen, die ihre Telefone aus dem Flugmodus nehmen, nachdem sie sie während des gesamten Fluges offline benutzt haben)
Wenn du den Verlauf der Ereigniszeit und der Verarbeitungszeit in einem realen System aufzeichnest, erhältst du in der Regel ein Ergebnis, das ein bisschen wie die rote Linie in Abbildung 1-1 aussieht.
In Abbildung 1-1 stellt die schwarze gestrichelte Linie mit der Steigung 1 den Idealfall dar, bei dem die Bearbeitungszeit und die Ereigniszeit genau gleich sind; die rote Linie stellt die Realität dar. In diesem Beispiel hinkt das System zu Beginn der Bearbeitungszeit ein wenig hinterher, nähert sich in der Mitte dem Ideal an und hinkt zum Ende hin wieder ein wenig hinterher. Auf den ersten Blick sind in diesem Diagramm zwei Arten von Schieflage zu erkennen, jeweils in unterschiedlichen Zeitbereichen:
- Bearbeitungszeit
-
Der vertikale Abstand zwischen der idealen und der roten Linie ist die Verzögerung im Bereich der Verarbeitungszeit. Dieser Abstand gibt an, wie viel Verzögerung (in der Verarbeitungszeit) zwischen dem Eintreten der Ereignisse zu einem bestimmten Zeitpunkt und ihrer Verarbeitung zu beobachten ist. Dies ist vielleicht die natürlichere und intuitivere der beiden Verschiebungen.
- Zeit der Veranstaltung
-
Der horizontale Abstand zwischen der idealen und der roten Linie ist der Betrag der Ereigniszeitverzögerung in der Pipeline zu diesem Zeitpunkt. Er gibt an, wie weit die Pipeline (in Bezug auf die Ereigniszeit) hinter dem Idealwert zurückliegt.
In Wirklichkeit sind die Verzögerung der Verarbeitungszeit und der Versatz der Ereigniszeit zu einem bestimmten Zeitpunkt identisch; sie sind nur zwei Arten, dieselbe Sache zu betrachten.5 Die wichtige Erkenntnis in Bezug auf Verzögerung/Verschiebung ist die folgende: Da die Zuordnung zwischen Ereigniszeit und Verarbeitungszeit nicht statisch ist (d.h. die Verzögerung/Verschiebung kann im Laufe der Zeit beliebig variieren), bedeutet dies, dass du deine Daten nicht nur im Kontext des Zeitpunkts analysieren kannst, zu dem sie von deiner Pipeline beobachtet werden, wenn du dich für ihre Ereigniszeiten interessierst (d.h. wann die Ereignisse tatsächlich eingetreten sind). Leider haben viele Systeme, die für unbegrenzte Daten entwickelt wurden, in der Vergangenheit auf diese Weise gearbeitet. Um mit der Unendlichkeit unbegrenzter Datensätze umzugehen, bieten diese Systeme in der Regel eine Art Fensterung der eingehenden Daten. Wir werden das Windowing später noch genauer besprechen, aber es bedeutet im Wesentlichen, dass ein Datensatz in endliche Teile entlang zeitlicher Grenzen zerlegt wird. Wenn du Wert auf Korrektheit legst und deine Daten im Kontext ihrer Ereigniszeiten analysieren willst, kannst du diese zeitlichen Grenzen nicht mit Hilfe der Verarbeitungszeit (d. h. der Verarbeitungszeitfenster) definieren, wie es viele Systeme tun. Da es keine konsistente Korrelation zwischen Verarbeitungszeit und Ereigniszeit gibt, werden einige deiner Ereigniszeitdaten in den falschen Verarbeitungszeitfenstern landen (aufgrund der inhärenten Verzögerung in verteilten Systemen, der Online-/Offline-Natur vieler Arten von Eingabequellen usw.), was die Korrektheit sozusagen aus dem Fenster wirft. In den folgenden Abschnitten und im Rest des Buches werden wir dieses Problem anhand einiger Beispiele genauer betrachten.
Leider ist das Bild auch nicht gerade rosig, wenn man nach Ereigniszeitpunkt fenstert. Im Zusammenhang mit unbegrenzten Daten führen Unordnung und Variablenverzerrung zu einem Vollständigkeitsproblem bei Ereigniszeitfenstern: Wenn es keine vorhersehbare Zuordnung zwischen Verarbeitungszeit und Ereigniszeit gibt, wie kannst du dann feststellen, wann du alle Daten für einen bestimmten Ereigniszeitpunkt X beobachtet hast? Bei vielen realen Datenquellen kannst du das einfach nicht. Die meisten der heute verwendeten Datenverarbeitungssysteme beruhen jedoch auf einer Art Vollständigkeitskonzept, was bei der Anwendung auf unbegrenzte Datenmengen einen großen Nachteil darstellt.
Ich schlage vor, dass wir nicht versuchen, unbegrenzte Daten zu endlichen Informationspaketen zusammenzufassen, die schließlich vollständig sind, sondern Werkzeuge entwickeln sollten, die es uns ermöglichen, in der Welt der Ungewissheit zu leben, die diese komplexen Datensätze mit sich bringen. Neue Daten werden eintreffen, alte Daten können zurückgezogen oder aktualisiert werden, und jedes System, das wir entwickeln, sollte in der Lage sein, mit diesen Tatsachen zurechtzukommen, wobei die Vorstellung von Vollständigkeit eher eine bequeme Optimierung für bestimmte und geeignete Anwendungsfälle ist als eine semantische Notwendigkeit für alle.
Bevor wir näher darauf eingehen, wie ein solcher Ansatz aussehen könnte, wollen wir noch einen weiteren nützlichen Hintergrund erläutern: allgemeine Datenverarbeitungsmuster.
Muster der Datenverarbeitung
Jetzt haben wir genug Hintergrundwissen, um uns die wichtigsten Nutzungsmuster anzusehen, die heute in der begrenzten und unbegrenzten Datenverarbeitung üblich sind. Wir betrachten beide Arten der Verarbeitung, und zwar im Kontext der beiden Haupttypen von Engines, die uns interessieren (Batch und Streaming, wobei ich Microbatch in diesem Zusammenhang im Wesentlichen mit Streaming in einen Topf werfe, weil die Unterschiede zwischen den beiden auf dieser Ebene nicht sehr wichtig sind).
Begrenzte Daten
Die Verarbeitung begrenzter Daten ist konzeptionell recht einfach und wahrscheinlich jedem bekannt. In Abbildung 1-2 beginnen wir auf der linken Seite mit einem Datensatz voller Entropie. Wir lassen ihn durch eine Datenverarbeitungsmaschine laufen (in der Regel eine Stapelverarbeitung, aber eine gut konzipierte Streaming-Engine würde genauso gut funktionieren), wie z. B. MapReduce, und erhalten auf der rechten Seite einen neuen strukturierten Datensatz mit einem höheren Eigenwert.
Obwohl es natürlich unendlich viele Variationen gibt, was man im Rahmen dieses Schemas tatsächlich berechnen kann, ist das Gesamtmodell recht einfach. Viel interessanter ist die Aufgabe, einen unbegrenzten Datensatz zu verarbeiten. Beginnen wir mit den Ansätzen, die bei traditionellen Batch-Engines verwendet werden, und enden mit den Ansätzen, die du mit einem System verwenden kannst, das für unbegrenzte Daten entwickelt wurde, wie die meisten Streaming- oder Microbatch-Engines.
Unbegrenzte Daten: Stapel
Batch-Engines wurden zwar nicht explizit für unbegrenzte Daten entwickelt, werden aber dennoch seit der Entwicklung von Batch-Systemen für die Verarbeitung unbegrenzter Datensätze eingesetzt. Wie zu erwarten, geht es bei solchen Ansätzen darum, die unbegrenzten Daten in eine Sammlung begrenzter Datensätze aufzuteilen, die für die Stapelverarbeitung geeignet sind.
Feste Fenster
Die gängigste Methode, einen unbegrenzten Datensatz durch wiederholte Läufe einer Batch-Engine zu verarbeiten, ist die Aufteilung der Eingabedaten in Fenster ( ) mit fester Größe ( ) und die anschließende Verarbeitung jedes dieser Fenster als separate, begrenzte Datenquelle (manchmal auch " Tumbling Windows" genannt), wie in Abbildung 1-3 dargestellt. Vor allem bei Eingabequellen wie Protokollen, bei denen Ereignisse in Verzeichnis- und Dateihierarchien geschrieben werden können, deren Namen das Fenster kodieren, dem sie entsprechen, erscheint dies auf den ersten Blick recht einfach, da du die Daten bereits im Vorfeld in die entsprechenden Ereignis-Zeit-Fenster eingeteilt hast.
In der Realität haben die meisten Systeme jedoch immer noch mit einem Vollständigkeitsproblem zu kämpfen (Was ist, wenn einige deiner Ereignisse auf dem Weg zu den Protokollen aufgrund einer Netzwerkpartition verzögert werden? Was ist, wenn deine Ereignisse global gesammelt werden und vor der Verarbeitung an einen gemeinsamen Ort übertragen werden müssen? Was ist, wenn deine Ereignisse von mobilen Geräten stammen?), was bedeutet, dass eine Art von Entschärfung notwendig sein kann (z. B. die Verzögerung der Verarbeitung, bis du sicher bist, dass alle Ereignisse erfasst wurden, oder die erneute Verarbeitung des gesamten Stapels für ein bestimmtes Zeitfenster, wenn die Daten zu spät eintreffen).
Sitzungen
Dieser Ansatz bricht noch mehr zusammen, wenn du versuchst, unbegrenzte Daten mit einer Batch-Engine in anspruchsvolleren Windowing-Strategien wie Sitzungen zu verarbeiten. Sitzungen sind in der Regel definiert als Zeiträume der Aktivität (z. B. für einen bestimmten Nutzer), die durch eine Lücke der Inaktivität beendet werden. Wenn du Sitzungen mit einer typischen Batch-Engine berechnest, erhältst du oft Sitzungen, die auf mehrere Batches aufgeteilt sind, wie die roten Markierungen in Abbildung 1-4 zeigen. Wir können die Anzahl der Splits reduzieren, indem wir die Batch-Größen erhöhen, allerdings auf Kosten einer höheren Latenzzeit. Eine andere Möglichkeit ist es, zusätzliche Logik hinzuzufügen, um Sitzungen aus früheren Läufen zusammenzufügen, aber das kostet zusätzliche Komplexität.
So oder so ist die Verwendung einer klassischen Batch-Engine zur Berechnung von Sitzungen nicht ideal. Ein besserer Weg wäre es, die Sitzungen im Streaming-Verfahren aufzubauen, was wir uns später ansehen werden.
Unbegrenzte Daten: Streaming
Im Gegensatz zur Ad-hoc-Natur der meisten Batch-basierten Ansätze zur Verarbeitung unbegrenzter Daten sind Streaming-Systeme für unbegrenzte Daten ausgelegt. Wie wir bereits erwähnt haben, hast du es bei vielen realen, verteilten Eingabequellen nicht nur mit unbegrenzten Daten zu tun, sondern auch mit Daten wie den folgenden:
-
Das bedeutet, dass du eine Art zeitbasiertes Shuffle in deiner Pipeline brauchst, wenn du die Daten in dem Kontext analysieren willst, in dem sie aufgetreten sind.
-
Das bedeutet, dass du nicht einfach davon ausgehen kannst, dass du den Großteil der Daten für einen bestimmten Ereigniszeitpunkt X innerhalb eines konstanten Epsilons der Zeit Y sehen wirst.
Es gibt eine Handvoll Ansätze, die du beim Umgang mit Daten, die diese Merkmale aufweisen, anwenden kannst. Ich unterteile diese Ansätze im Allgemeinen in vier Gruppen: zeitunabhängig, Annäherung, Fensterung nach Verarbeitungszeit und Fensterung nach Ereigniszeit.
Schauen wir uns nun jeden dieser Ansätze etwas genauer an.
Zeitunabhängig
Die zeitunabhängige Verarbeitung wird für Fälle verwendet, in denen die Zeit im Wesentlichen irrelevant ist, d.h. alle relevanten Logiken sind datengesteuert. Da bei solchen Anwendungsfällen alles vom Eintreffen weiterer Daten abhängt, muss eine Streaming-Engine eigentlich nichts anderes unterstützen als die grundlegende Datenlieferung. Daher unterstützen praktisch alle existierenden Streaming-Systeme von Haus aus zeitunabhängige Anwendungsfälle (abgesehen von den unterschiedlichen Konsistenzgarantien von System zu System, wenn dir die Korrektheit wichtig ist). Batch-Systeme eignen sich auch gut für die zeitunabhängige Verarbeitung unbegrenzter Datenquellen, indem sie die unbegrenzte Quelle einfach in eine beliebige Abfolge begrenzter Datensätze zerlegen und diese Datensätze unabhängig voneinander verarbeiten. In diesem Abschnitt sehen wir uns ein paar konkrete Beispiele an, aber da die zeitunabhängige Verarbeitung (zumindest aus zeitlicher Sicht) so einfach ist, werden wir uns nicht weiter damit befassen.
Filtern
Eine sehr grundlegende Form der zeitunabhängigen Verarbeitung ist das Filtern, ein Beispiel dafür ist in Abbildung 1-5 dargestellt. Stell dir vor, du verarbeitest Web-Traffic-Protokolle und möchtest den gesamten Traffic herausfiltern, der nicht von einer bestimmten Domain stammt. Du würdest dir jeden Datensatz ansehen, sobald er ankommt, und prüfen, ob er zu der gewünschten Domäne gehört, und ihn gegebenenfalls löschen. Da dies nur von einem einzigen Element abhängt, ist die Tatsache, dass die Datenquelle unbegrenzt, ungeordnet und zeitlich versetzt ist, irrelevant.
Innere Fugen
Ein weiteres zeitunabhängiges Beispiel ist ein Inner Join, der in Abbildung 1-6 dargestellt ist. Wenn du dich bei der Verknüpfung zweier unbegrenzter Datenquellen nur für die Ergebnisse der Verknüpfung interessierst, wenn ein Element aus beiden Quellen eintrifft, gibt es kein zeitliches Element in der Logik. Wenn du einen Wert aus einer Quelle siehst, kannst du ihn einfach im persistenten Zustand zwischenspeichern; erst wenn der zweite Wert aus der anderen Quelle eintrifft, musst du den verbundenen Datensatz ausgeben. (In Wirklichkeit bräuchtest du wahrscheinlich eine Art Speicherbereinigung für nicht übermittelte Teil-Joins, die wahrscheinlich zeitabhängig ist. Aber für einen Anwendungsfall mit wenig oder gar keinen unvollständigen Joins dürfte das kein Problem sein).
Die Umstellung der Semantik auf eine Art Outer-Join führt zu dem Problem der Datenvollständigkeit, über das wir bereits gesprochen haben: Wenn du eine Seite des Joins gesehen hast, woher weißt du dann, ob die andere Seite jemals ankommen wird oder nicht? Um ehrlich zu sein, weißt du das nicht, also musst du eine Art Zeitüberschreitung einführen, die ein Zeitelement mit sich bringt. Dieses Zeitelement ist im Wesentlichen eine Form von Windowing, auf das wir gleich noch näher eingehen werden.
Algorithmen zur Annäherung
Die zweite große Kategorie von Ansätzen ( ) sind Näherungsalgorithmen, wie z. B. approximatives Top-N, Streaming k-means und so weiter. Sie nehmen eine unbegrenzte Menge an Eingabedaten auf und liefern Ausgabedaten, die mehr oder weniger wie das aussehen, was du dir erhofft hast, wie in Abbildung 1-7 zu sehen ist. Der Vorteil von Näherungsalgorithmen ist, dass sie von vornherein mit wenig Aufwand verbunden und für unbegrenzte Daten ausgelegt sind. Die Nachteile sind, dass es nur eine begrenzte Anzahl von Algorithmen gibt, dass die Algorithmen selbst oft kompliziert sind (was die Entwicklung neuer Algorithmen erschwert) und dass ihr Nutzen durch ihre Näherungsfunktion eingeschränkt ist.
Es ist erwähnenswert, dass diese Algorithmen in der Regel ein gewisses Zeitelement in ihrem Design haben (z. B. eine Art eingebautes Decay). Und da sie Elemente verarbeiten, sobald sie eintreffen, ist dieses Zeitelement in der Regel verarbeitungszeitbasiert. Das ist besonders wichtig für Algorithmen, die nachweisbare Fehlergrenzen für ihre Annäherungen vorgeben. Wenn diese Fehlergrenzen davon ausgehen, dass die Daten in der richtigen Reihenfolge eintreffen, sind sie praktisch bedeutungslos, wenn du den Algorithmus mit ungeordneten Daten fütterst, bei denen die Ereignisse zeitlich versetzt sind. Das solltest du im Hinterkopf behalten.
Approximationsalgorithmen sind ein faszinierendes Thema, aber da sie im Wesentlichen ein weiteres Beispiel für zeitunabhängige Verarbeitung sind (abgesehen von den zeitlichen Merkmalen der Algorithmen selbst), sind sie recht einfach zu handhaben und daher angesichts unseres aktuellen Schwerpunkts nicht weiter beachtenswert.
Fenstern
Die beiden verbleibenden Ansätze für die unbegrenzte Datenverarbeitung sind beide Varianten des Windowing. Bevor ich auf die Unterschiede zwischen ihnen eingehe, sollte ich klarstellen, was genau ich mit Windowing meine, da wir es im vorherigen Abschnitt nur kurz angeschnitten haben. Beim Windowing geht es einfach darum, eine (unbegrenzte oder begrenzte) Datenquelle entlang zeitlicher Grenzen in begrenzte Teile zu zerlegen und zu verarbeiten. Abbildung 1-8 zeigt drei verschiedene Windowing-Muster.
Schauen wir uns die einzelnen Strategien genauer an:
- Feste Fenster (auch Kippfenster genannt)
-
Wir haben bereits über feste Fenster gesprochen. Feste Fenster unterteilen die Zeit in Segmente mit einer festen zeitlichen Länge. In der Regel werden die Segmente für feste Fenster (wie in Abbildung 1-9 dargestellt) gleichmäßig auf den gesamten Datensatz angewendet, was ein Beispiel für ausgerichtete Fenster ist. In manchen Fällen ist es wünschenswert, die Fenster für verschiedene Teilmengen der Daten (z. B. pro Schlüssel) phasenverschoben anzuwenden, um die Auslastung der Fenster gleichmäßiger über die Zeit zu verteilen.6
- Schiebefenster (auch Hopping-Fenster genannt)
-
Schiebefenster sind eine Verallgemeinerung der festen Fenster und werden durch eine feste Länge und eine feste Periode definiert. Wenn die Periode kleiner als die Länge ist, überlappen sich die Fenster. Wenn die Periode gleich der Länge ist, hast du feste Fenster. Und wenn die Periode größer als die Länge ist, hast du eine Art Stichprobenfenster, das nur Teilmengen der Daten im Laufe der Zeit betrachtet. Wie feste Fenster sind auch Schiebefenster in der Regel ausgerichtet, aber in bestimmten Fällen können sie zur Leistungsoptimierung auch nicht ausgerichtet sein. Beachte, dass die gleitenden Fenster in Abbildung 1-8 so gezeichnet sind, wie sie sind, um ein Gefühl der gleitenden Bewegung zu vermitteln; in Wirklichkeit würden alle fünf Fenster für den gesamten Datensatz gelten.
- Sitzungen
-
Ein Beispiel für dynamische Fenster sind Sitzungen, die aus einer Reihe von Ereignissen bestehen, die durch eine Inaktivitätspause beendet werden, die größer ist als eine bestimmte Zeitspanne. Sitzungen werden häufig zur Analyse des Nutzerverhaltens im Zeitverlauf verwendet, indem eine Reihe von zeitlich zusammenhängenden Ereignissen zusammengefasst wird (z. B. eine Folge von Videos, die in einer Sitzung angesehen werden). Sitzungen sind interessant, weil ihre Länge nicht von vornherein festgelegt werden kann; sie hängt von den tatsächlichen Daten ab. Sie sind auch das Paradebeispiel für unausgerichtete Fenster, weil Sitzungen in verschiedenen Teilmengen von Daten (z. B. verschiedenen Nutzern) praktisch nie identisch sind.
Die beiden Zeitbereiche, die wir zuvor besprochen haben (Verarbeitungszeit und Ereigniszeit), sind im Wesentlichen die beiden, die uns interessieren.7 Windowing ist in beiden Bereichen sinnvoll, also schauen wir uns beide im Detail an und sehen, wie sie sich unterscheiden. Da das Windowing der Verarbeitungszeit in der Vergangenheit häufiger vorkam, fangen wir dort an.
Fensterung nach Bearbeitungszeit
Bei der Fensterung nach Verarbeitungszeit puffert das System die eingehenden Daten in Fenstern, bis eine bestimmte Verarbeitungszeit verstrichen ist. Bei festen Fünf-Minuten-Fenstern zum Beispiel würde das System die Daten fünf Minuten lang puffern. Danach würde es alle Daten, die es in diesen fünf Minuten beobachtet hat, als Fenster behandeln und sie zur Verarbeitung nach unten schicken.
Es gibt ein paar nette Eigenschaften von Verarbeitungszeitfenstern:
-
Es ist einfach. Die Umsetzung ist extrem einfach, denn du musst dich nicht um die zeitliche Verschiebung von Daten kümmern. Du pufferst die Daten einfach, wenn sie ankommen, und schickst sie weiter, wenn sich das Fenster schließt.
-
Die Beurteilung der Vollständigkeit von Fenstern ist einfach. Da das System perfekt weiß, ob alle Eingaben für ein Fenster gesehen wurden, kann es perfekt entscheiden, ob ein bestimmtes Fenster vollständig ist. Das bedeutet, dass es bei der Fenstereinteilung nach Bearbeitungszeit nicht mit "späten" Daten umgehen muss.
-
Wenn du Informationen über die Quelle ableiten willst , während sie beobachtet wird, ist das Processing-Time-Windowing genau das Richtige für dich. Viele Überwachungsszenarien fallen in diese Kategorie. Stell dir vor, du verfolgst die Anzahl der Anfragen pro Sekunde, die an einen globalen Webservice gesendet werden. Die Berechnung einer Rate dieser Anfragen zur Erkennung von Ausfällen ist ein perfekter Anwendungsfall für das Processing-Time-Windowing.
Abgesehen von den guten Argumenten gibt es einen großen Nachteil der Verarbeitungszeitfenster: Wenn die fraglichen Daten mit Ereigniszeiten verknüpft sind, müssen diese Daten in der Reihenfolge der Ereigniszeiten ankommen, wenn die Verarbeitungszeitfenster die Realität widerspiegeln sollen, wann diese Ereignisse tatsächlich eingetreten sind. Leider sind ereigniszeitlich geordnete Daten in vielen realen, verteilten Eingabequellen unüblich.
Ein einfaches Beispiel: Stell dir eine mobile App vor, die Nutzungsstatistiken für die spätere Verarbeitung sammelt. Wenn ein bestimmtes mobiles Gerät für eine beliebige Zeit offline ist (kurzzeitiger Verbindungsabbruch, Flugzeugmodus während eines Fluges über das Land usw.), werden die in dieser Zeit aufgezeichneten Daten erst hochgeladen, wenn das Gerät wieder online ist. Das bedeutet, dass die Daten mit einer Zeitverzögerung von Minuten, Stunden, Tagen, Wochen oder mehr ankommen können. Es ist praktisch unmöglich, aus einem solchen Datensatz irgendwelche nützlichen Schlüsse zu ziehen, wenn er nach der Verarbeitungszeit geordnet ist.
Ein weiteres Beispiel ist, dass viele verteilte Eingangsquellen scheinbar ereigniszeitlich geordnete (oder nahezu geordnete) Daten liefern, wenn das Gesamtsystem gesund ist. Leider bedeutet die Tatsache, dass die Ereigniszeitverzerrung für die Eingabequelle im gesunden Zustand gering ist, nicht, dass dies immer so bleibt. Stell dir einen globalen Dienst vor, der Daten verarbeitet, die auf mehreren Kontinenten gesammelt wurden. Wenn Netzwerkprobleme auf einer transkontinentalen Leitung mit eingeschränkter Bandbreite (die leider überraschend häufig auftreten) die Bandbreite weiter verringern und/oder die Latenzzeit erhöhen, kann es passieren, dass ein Teil deiner Eingabedaten plötzlich mit einer viel größeren Verzerrung ankommt als zuvor. Wenn du diese Daten nach der Verarbeitungszeit in Fenster einteilst, sind deine Fenster nicht mehr repräsentativ für die Daten, die tatsächlich in ihnen enthalten sind; stattdessen stellen sie die Zeitfenster dar, in denen die Ereignisse in der Verarbeitungspipeline ankamen, die eine willkürliche Mischung aus alten und aktuellen Daten ist.
Was wir in beiden Fällen wirklich wollen, ist, dass die Daten nach ihren Ereigniszeiten geordnet werden, und zwar so, dass die Reihenfolge des Eintreffens der Ereignisse nicht beeinflusst wird. Was wir wirklich wollen, ist ein Ereignis-Zeit-Fenster.
Fensterung nach Ereigniszeit
Ereignis-Zeit-Fenster werden verwendet, wenn du eine Datenquelle in endlichen Abschnitten beobachten musst, die die Zeiten widerspiegeln, zu denen die Ereignisse tatsächlich eingetreten sind. Es ist der Goldstandard des Windowing. Vor 2016 hatten die meisten Datenverarbeitungssysteme keine native Unterstützung dafür (obwohl jedes System mit einem vernünftigen Konsistenzmodell, wie Hadoop oder Spark Streaming 1.x, als vernünftiges Substrat für den Aufbau eines solchen Windowing-Systems dienen konnte). Heute sieht die Welt zum Glück ganz anders aus: Mehrere Systeme, von Flink über Spark und Storm bis hin zu Apex, unterstützen eine Art von Event-Time-Windowing von Haus aus.
Abbildung 1-10 zeigt ein Beispiel für die Einteilung einer unbegrenzten Quelle in feste Ein-Stunden-Fenster.
Die schwarzen Pfeile in Abbildung 1-10 weisen auf zwei besonders interessante Daten hin. Beide kamen in Verarbeitungszeitfenstern an, die nicht mit den Ereigniszeitfenstern übereinstimmten, zu denen die Daten gehörten. Wären diese Daten für einen Anwendungsfall, bei dem es auf die Ereigniszeiten ankommt, in Verarbeitungszeitfenster eingeteilt worden, wären die berechneten Ergebnisse nicht korrekt gewesen. Wie du dir denken kannst, ist die Korrektheit der Ereigniszeit ein Vorteil der Verwendung von Ereigniszeitfenstern.
Ein weiterer Vorteil von Event-Time-Windowing über eine unbegrenzte Datenquelle ist, dass du dynamisch große Fenster, wie z. B. Sessions, erstellen kannst, ohne dass es zu willkürlichen Unterteilungen kommt, wie sie bei der Erstellung von Sessions über feste Fenster zu beobachten sind (wie wir zuvor im Session-Beispiel aus "Unbounded Data" gesehen haben ): Streaming"), wie in Abbildung 1-11 gezeigt.
Natürlich gibt es mächtige Semantiken selten umsonst, und Ereignis-Zeit-Fenster sind da keine Ausnahme. Ereignis-Zeit-Fenster haben zwei bemerkenswerte Nachteile, die darauf zurückzuführen sind, dass Fenster oft länger leben müssen (in der Verarbeitungszeit) als die tatsächliche Länge des Fensters selbst:
- Pufferung
-
Aufgrund der verlängerten Lebensdauer von Fenstern ist mehr Pufferung von Daten erforderlich. Glücklicherweise ist die persistente Speicherung in der Regel die billigste der Ressourcen, auf die die meisten Datenverarbeitungssysteme angewiesen sind (die anderen sind hauptsächlich CPU, Netzwerkbandbreite und RAM). Daher ist dieses Problem in der Regel weniger problematisch, als du vielleicht denkst, wenn du ein gut durchdachtes Datenverarbeitungssystem mit einem konsistenten persistenten Zustand und einer guten In-Memory-Caching-Schicht verwendest. Außerdem muss für viele nützliche Aggregationen nicht die gesamte Eingabemenge gepuffert werden (z. B. Summe oder Durchschnitt), sondern sie können inkrementell durchgeführt werden, wobei eine viel kleinere Zwischenaggregation im persistenten Zustand gespeichert wird.
- Vollständigkeit
-
Da wir oft nicht wissen können, wann wir alle Daten für ein bestimmtes Fenster gesehen haben, wie können wir wissen, wann die Ergebnisse für das Fenster bereit sind? In Wahrheit wissen wir das einfach nicht. Für viele Arten von Eingaben kann das System eine ziemlich genaue heuristische Schätzung der Fertigstellung des Fensters abgeben, etwa mit Hilfe der Wasserzeichen in MillWheel, Cloud Dataflow und Flink (auf die wir in den Kapiteln 3 und 4 näher eingehen). In Fällen, in denen absolute Korrektheit von größter Bedeutung ist (z. B. bei der Rechnungsstellung), besteht die einzige echte Option darin, dem Pipeline-Ersteller die Möglichkeit zu geben, anzugeben, wann die Ergebnisse für die Fenster materialisiert werden sollen und wie diese Ergebnisse im Laufe der Zeit verfeinert werden sollen. Der Umgang mit der Vollständigkeit von Fenstern (oder deren Fehlen) ist ein faszinierendes Thema, das aber am besten anhand konkreter Beispiele erforscht werden kann, auf die wir im Folgenden eingehen.
Zusammenfassung
Uff! Das war eine Menge an Informationen. Wenn du es bis hierher geschafft hast, gebührt dir ein großes Lob! Aber wir haben gerade erst angefangen. Bevor wir uns das Balkenmodell im Detail ansehen, sollten wir kurz zurücktreten und das bisher Gelernte rekapitulieren. In diesem Kapitel haben wir das Folgende gemacht:
-
Wir haben die Terminologie geklärt, indem wir die Definition von "Streaming" auf Systeme konzentriert haben, die für unbegrenzte Daten entwickelt wurden, und gleichzeitig beschreibendere Begriffe wie "ungefähre/spekulative Ergebnisse" für verschiedene Konzepte verwendet haben, die oft unter dem Begriff "Streaming" zusammengefasst werden. Darüber hinaus haben wir zwei wichtige Dimensionen großer Datensätze hervorgehoben: die Kardinalität (d. h. begrenzt oder unbegrenzt) und die Beschaffenheit (d. h. Tabelle oder Stream), die einen Großteil der zweiten Hälfte des Buches ausmachen wird.
-
Er hat die relativen Fähigkeiten von gut konzipierten Batch- und Streaming-Systemen bewertet und dabei festgestellt, dass Streaming eine strenge Obermenge von Batch ist und dass Konzepte wie die Lambda-Architektur ( ), die davon ausgehen, dass Streaming gegenüber Batch unterlegen ist, mit der zunehmenden Reife von Streaming-Systemen überflüssig werden.
-
Er schlug zwei übergeordnete Konzepte vor, die notwendig sind, damit Streaming-Systeme zu Batch-Systemen aufschließen und sie schließlich übertreffen können: Korrektheit und Werkzeuge für das Denken über Zeit.
-
Er stellte die wichtigen Unterschiede zwischen Ereigniszeit und Verarbeitungszeit fest, beschrieb die Schwierigkeiten, die diese Unterschiede bei der Analyse von Daten im Zusammenhang mit dem Zeitpunkt ihres Auftretens mit sich bringen, und schlug eine Verlagerung des Ansatzes weg vom Begriff der Vollständigkeit und hin zur einfachen Anpassung an die Veränderungen der Daten im Laufe der Zeit vor.
-
Wir haben uns die wichtigsten Datenverarbeitungsansätze angeschaut, die heute für begrenzte und unbegrenzte Daten verwendet werden, sowohl über Batch- als auch über Streaming-Engines, und haben die unbegrenzten Ansätze grob in folgende Kategorien eingeteilt: zeitunabhängig, Annäherung, Fensterung nach Verarbeitungszeit und Fensterung nach Ereigniszeit.
Als Nächstes tauchen wir in die Details des Beam-Modells ein und werfen einen konzeptionellen Blick darauf, wie wir den Begriff der Datenverarbeitung in vier zusammenhängende Achsen unterteilt haben: was, wo, wann und wie. Außerdem werfen wir einen detaillierten Blick auf die Verarbeitung eines einfachen, konkreten Beispieldatensatzes in verschiedenen Szenarien, um die Vielfalt der Anwendungsfälle zu verdeutlichen, die das Beam-Modell ermöglicht. Diese Beispiele helfen dabei, die in diesem Kapitel vorgestellten Begriffe Ereigniszeit und Verarbeitungszeit zu verdeutlichen und gleichzeitig neue Konzepte wie Wasserzeichen kennenzulernen.
1 Der Vollständigkeit halber sollte erwähnt werden, dass diese Definition sowohl echtes Streaming als auch Microbatch-Implementierungen umfasst. Für diejenigen unter euch, die mit Microbatch-Systemen nicht vertraut sind: Es handelt sich um Streaming-Systeme, die wiederholte Ausführungen einer Stapelverarbeitungs-Engine verwenden, um unbegrenzte Daten zu verarbeiten. Spark Streaming ist das berühmteste Beispiel in der Branche.
2 Leser, die mit meinem ursprünglichen Artikel "Streaming 101" vertraut sind, werden sich vielleicht daran erinnern, dass ich nachdrücklich dafür plädiert habe, den Begriff "Stream" bei der Bezeichnung von Datensätzen aufzugeben. Das hat sich nie durchgesetzt, was ich zunächst für seine Eingängigkeit und den weit verbreiteten Gebrauch hielt. Im Nachhinein denke ich jedoch, dass ich mich einfach geirrt habe. Die Unterscheidung zwischen den beiden verschiedenen Arten von Datensätzen - Tabellen und Streams - ist tatsächlich sehr sinnvoll. Der größte Teil der zweiten Hälfte dieses Buches ist dem Verständnis der Beziehung zwischen diesen beiden Typen gewidmet.
3 Wenn du nicht weißt, was ich mit " Exact-once" meine, dann ist damit eine bestimmte Art von Konsistenzgarantie gemeint, die bestimmte Datenverarbeitungs-Frameworks bieten. Konsistenzgarantien werden in der Regel in drei Hauptklassen eingeteilt: at-most-once processing, at-least-once processing und exactly-once processing. Die hier verwendeten Bezeichnungen beziehen sich auf die effektive Semantik der von der Pipeline erzeugten Ergebnisse und nicht auf die tatsächliche Anzahl der Verarbeitungen, die eine Pipeline für einen bestimmten Datensatz vornimmt (oder versucht, vorzunehmen). Aus diesem Grund wird manchmal der Begriff "effektiv-einmal" anstelle von "genau-einmal" verwendet, da er die zugrunde liegende Natur der Dinge besser wiedergibt. Reuven behandelt diese Konzepte in Kapitel 5 ausführlicher.
4 Seit der ursprünglichen Veröffentlichung von "Streaming 101" haben mich zahlreiche Personen darauf hingewiesen, dass es intuitiver gewesen wäre, die Verarbeitungszeit auf die x-Achse und die Ereigniszeit auf die y-Achse zu setzen. Ich stimme zu, dass sich das Vertauschen der beiden Achsen zunächst natürlicher anfühlen würde, da die Ereigniszeit wie die abhängige Variable im Vergleich zur unabhängigen Variable der Verarbeitungszeit wirkt. Da beide Variablen jedoch monoton und eng miteinander verbunden sind, sind sie tatsächlich voneinander abhängig. Ich denke, aus technischer Sicht musst du dich einfach für eine Achse entscheiden und dabei bleiben. Mathematik ist verwirrend (vor allem außerhalb Nordamerikas, wo sie plötzlich im Plural auftaucht und dich überrumpelt).
5 Dieses Ergebnis sollte eigentlich nicht überraschen (für mich war es das aber, deshalb weise ich darauf hin), denn wir bilden mit der Ideallinie ein rechtwinkliges Dreieck, wenn wir die beiden Arten der Schräglage messen. Mathe ist cool.
6 Wir betrachten ausgerichtete feste Fenster im Detail in Kapitel 2 und nicht ausgerichtete feste Fenster in Kapitel 4.
7 Wenn du in der akademischen Literatur oder in SQL-basierten Streaming-Systemen genug stöberst, wirst du auch auf einen dritten Zeitbereich für die Fensterung stoßen: die tupelbasierte Fensterung (d.h. Fenster, deren Größe in Anzahl der Elemente gezählt wird). Die tupelbasierte Fensterung ist jedoch im Wesentlichen eine Form der Verarbeitungszeitfensterung, bei der den Elementen monoton steigende Zeitstempel zugewiesen werden, wenn sie im System ankommen. Daher werden wir das tupelbasierte Windowing nicht weiter ausführen.
Get Streaming-Systeme 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.