Ereignisbasierte Systeme mit Cloud Spanner bereitstellen

In diesem Artikel wird am Beispiel eines Architekturmusters erläutert, wie Sie Spanner als System zur Ereignisaufnahme und Pub/Sub als Ereignisverzeichnis verwenden und damit ein System erstellen, das Folgendes kann:

  • In eine hochverfügbare Ereignisquelle schreiben.
  • Diese Schreibvorgänge als Ereignisse veröffentlichen, die andere Systeme verwenden können.
  • Ereignisse für die Wiedergabe archivieren.
  • Ereignisse zur Analyse in ein System laden.
  • Ereignisse für schnelle Abfrage in ein System filtern.

Dieser Artikel richtet sich an Softwareentwickler, die sich über die Anwendungsmöglichkeiten und Komponenten eines ereignisbasierten Systems und die damit verbundenen Kompromisse informieren möchten. Sie erfahren, wie Sie mithilfe von Cloud Functions verschiedene Anwendungen und Dienste erstellen, die Ihre ereignisbasierte Architektur unterstützen.

Anwendungsfälle und Entwicklungskriterien

Sie können dieses Architekturmuster immer dann verwenden, wenn Sie neue Daten in eine Datenquelle schreiben müssen, in diesem Fall Spanner.

Anwendungsfälle

Dieses Muster ist in folgenden Szenarien nützlich:

  • Warenkorb im E-Commerce-Bereich
  • Auftragsverwaltung und Lieferkette
  • Wallets, Zahlungs- und Gebührenabwicklung

Das folgende Schaubild stellt den Ablauf in einem E-Commerce-Warenkorbsystem dar.

Der Ablauf von Ereignissen in einem E-Commerce-Warenkorb

In komplexen Systemen, zum Beispiel den Bereichen E-Commerce und Zahlungen, ist es hilfreich, Transaktionen mithilfe ereignisbasierter Architekturen zu verfolgen. Wenn ein Kunde beispielsweise einen Artikel in einen Warenkorb legt oder eine Kreditkarte für die Bezahlung benutzt, sollten Sie mehrere nachgelagerte Prozesse auslösen, mit denen überprüft wird, ob der Artikel vorrätig ist und der Kunde Guthaben auf seinem Konto hat. Sie sollten dafür sorgen, dass Kunden jederzeit Bestellungen aufgeben können, denn davon hängt Ihr Geschäft ab.

Entwicklungskriterien

Die folgenden Beispiele zeigen Entwicklungskriterien, die den Einsatz einer ereignisbasierten Systemarchitektur nahelegen:

  • Beim Schreiben von Bestellungen und Zahlungen in das System muss hohe Verfügbarkeit gewährleistet sein.
  • Es muss sichergestellt sein, dass Ihre Kunden die bestellten Artikel (und nur die bestellten Artikel) erhalten und dass ihnen der richtige Betrag für den Kauf in Rechnung gestellt wird.
  • Es muss deterministische Fehlermodi geben (d. h., Sie müssen sicher sein, dass ein Schreibvorgang entweder fehlgeschlagen oder erfolgreich ist).
  • Es muss einen Mechanismus geben, abhängige Dienste darüber zu informieren, dass ein für sie interessanter Schreibvorgang im System stattgefunden hat.

In diesem Artikel wird ein System beschrieben, das all diese Anforderungen erfüllt und so flexibel ist, dass später weitere Funktionen aufgenommen werden können.

Systemarchitektur

Als Erstes benötigen Sie einen Dienst, der bei der Annahme von Schreibvorgängen hohe Verfügbarkeit gewährleistet. Dieses System muss auch deterministische Fehlermodi bieten. Es muss erkennen, wenn ein Schreibvorgang fehlschlägt, sodass Ihr Schreibprozess den Schreibvorgang wiederholen kann, ohne dass möglicherweise ganz oder teilweise doppelt geschrieben wird.

Die kanonische Anwendung für dieses Szenario ist eine Datenbank, die Transaktionen mit Atomarität, Konsistenz, Isolierung und Dauerhaftigkeit (ACID) unterstützt. Es ist aber schwierig, Datenbanken hochverfügbar zu machen, insbesondere für Schreibvorgänge. Die Replikation kann zu Inkonsistenzen bei den Daten führen und die Architektur teurer und komplexer machen. Wachsende Komplexität ist das größte Designrisiko, wenn es auf hohe Verfügbarkeit ankommt.

Darüber hinaus können typische auf Hochverfügbarkeit ausgelegte Datenbankkonfigurationen Ausfälle in Verfügbarkeitszonen nicht ohne Replikationsverzögerungen verkraften. Wenn die Verzögerungen zunehmen, wird es auch wahrscheinlicher, dass zusätzliche Fehler zum Verlust aller Schreibvorgänge führen, die gerade auf den Replikatknoten in einer anderen Verfügbarkeitszone übertragen werden. Diese zusätzlichen Fehler führen dazu, dass die Schreibvorgänge vor dem Ausfall in dieser Zone nicht vollständig in das Replikat geschrieben werden.

Architekturaufbau

Im folgenden Schaubild sehen Sie eine ereignisbasierte Architektur mit Spanner, die dafür entwickelt wurde, die Probleme der Komplexität und der Kosten traditioneller Hochverfügbarkeitsdatenbanken zu lösen.

Schaubild einer ereignisbasierten Architektur mit Spanner.

Diese ereignisbasierte Architektur stützt sich auf die folgenden Komponenten, die Sie mithilfe von Cloud Functions erstellen. Dabei handelt es sich um Anwendungen und Cloud Functions-Funktionen.

  • Abfrageanwendung: Fragt Spanner ab, konvertiert das Datensatzformat in Avro und veröffentlicht es in Pub/Sub.
  • archiver: Ruft Ereignisse ab, die durch in einem Pub/Sub-Thema veröffentlichte Nachrichten ausgelöst werden, und schreibt diese Datensätze in ein globales Archiv in Cloud Storage.
  • bqloader: Wird ausgelöst, wenn Datensätze in Cloud Storage geschrieben wurden, und lädt diese Datensätze in eine entsprechende BigQuery-Tabelle.
  • janitor: Liest alle Einträge, die in das globale Archiv geschrieben wurden, mit einer festen Rate und komprimiert sie dann zur langfristigen Speicherung.
  • replayer: Liest die Datensätze in der angegebenen Reihenfolge aus dem Langzeitspeicher, entpackt sie und lädt sie in einen neuen Pub/Sub-Stream.
  • Materialisierer-Anwendung: Filtert Datensätze, die in Pub/Sub geschrieben wurden, und lädt sie für einfachen Abfragezugriff in eine entsprechende Redis-Datenbank (materialisierte Datenansicht).

Einen Abfragedienst erstellen

Wenn Sie ein System haben, das Schreibvorgänge akzeptiert, müssen Ihre nachgeordneten Dienste jedes Mal benachrichtigt werden, wenn etwas in das System geschrieben wird.

Bei traditionellen Datenbanken gibt es verschiedene Möglichkeiten, aber in der Regel wird dafür das Write-Ahead-Log (WAL) oder der CDC-Stream (Change Data Capture) einer Datenbank verfolgt. Das Format dieser Lösungen ist nicht gut lesbar. Das Format wurde dafür entwickelt, die Änderungen am Datensatz darzustellen und zu streamen. Es war nicht dazu gedacht, ein nachgelagertes Ereignissystem zu informieren und den relevanten Kontext dieses Ereignisses weiterzuleiten. Nur eine binäre Darstellung der Änderungen ohne den vollständigen Datensatz ist in den meisten Situationen nicht brauchbar. Ein weiterer Nachteil dieses Formats ist, dass es für Menschen nicht lesbar ist. Das erschwert die Fehlerbehebung und die Überprüfung des Streams sehr.

Sie können stattdessen etwas entwickeln, das die Datenbank nach allen neuen Einträgen abfragt und diese dann an das nachgelagerte System weiterleitet. Abfragedienste sind eine gängige Methode, Datensätze zu verarbeiten, die neu in eine Datenbank geschrieben werden. Das hat folgende Vorteile:

  • Sie sind einfach zu verstehen und zu schreiben.
  • Der Overhead ist gering, wenn sie richtig geschrieben sind.
  • Sie sind stabil und unabhängig.
  • Sie sind fehlertolerant, sowohl bei Abfragen als auch beim Parsen von Datensätzen.
  • Bei der Art, wie Änderungen gefiltert und dargestellt werden, sind sie flexibel.

Wenn man einen Abfragedienst schreibt, anstatt herkömmlich mit WAL oder CDC zu arbeiten, muss man unter anderem folgende Kompromisse in Kauf nehmen:

  • Abfragen der Datenbank in kurzen Intervallen (weniger als einer Sekunde) können die Datenbank stärker belasten.
  • Je nach Tabellenlayout, verwendeter Abfrage und danach, wie viele andere Apps Ihre Datenbank gleichzeitig abfragen, kann ein Abfragedienst Ressourcenkonflikte (Sperren) mit anderen Anwendungen verursachen.
  • Für den Abfragedienst brauchen Sie möglicherweise größere Datenbankmaschinen und teureren Speicher wie SSD, um die zusätzliche Last und die Ressourcenkonflikte zu bewältigen.

Sie können einige dieser Nachteile in Spanner verringern, wenn Sie Nur-Lese-Transaktionen für Ihre Leseabfragen verwenden und Best Practices für effiziente und effektive SQL-Abfragen einhalten.

Mit dem Commit-Zeitstempel in Spanner können Sie alle neuen Datensätze aus einem bestimmten Zeitraum ermitteln. Der Commit-Zeitstempel beruht auf der TrueTime-Technologie und ist für Spanner eine global einheitliche Angabe dafür, wann der Commit für einen Schreibvorgang in die Datenbank ausgeführt wurde. Spanner kann Schreibvorgänge in vielen Regionen der Welt annehmen und Ihr Abfragedienst kann ein Verzeichnis erstellen, das alle Ereignisse in der richtigen Reihenfolge enthält.

Cloud Functions zur Darstellung von Aufgaben verwenden

Cloud Functions ist eine ereignisgesteuerte, serverlose Rechenplattform in Google Cloud. Diese Funktionen sind zustandslose Codeteile, die als Antwort auf einen Auslöser (z. B. eine HTTP-Anfrage oder einen Ereignisauslöser) ausgeführt werden. Bei ereignisbasierten Systemen stellen Cloud Functions-Funktionen normalerweise die einzelnen Aufgaben dar, die mit der Veröffentlichung eines Ereignisses verbunden sind. Da sie serverlos sind, skalieren sie mit dem Anfrageaufkommen und erfordern kein zusätzliches operatives Eingreifen.

Cloud Functions-Funktionen haben einige Nachteile:

  • Die Reaktionszeiten können uneinheitlich sein.
  • Logging und Tracing können schwieriger sein, weil Start und Ausführung so kurzlebig sind.
  • Sie müssen zustandslos und idempotent sein, da der Wiederholungsversuch bei einem Fehler in der Regel automatisch erfolgt.
  • Je nach Status der Logging-Umgebung kann es schwierig sein, Fehler lokal zu beheben und Fehler zu reproduzieren.

Pub/Sub als Verzeichnis verwenden

Ein Verzeichnis (Ledger) ist eine Ereignisliste, bei der nur Anfügevorgänge möglich sind. Sie wird auf einem Ereignisbus oder einer Nachrichtenwarteschlange veröffentlicht. Sie können ein Ereignis abonnieren oder verfolgen, indem Sie entweder einen bestimmten Ereignisbus oder ein Nachrichtenwarteschlangenthema abonnieren oder die Gesamtheit aller Nachrichten nach dem Substantiv, dem Verb oder bestimmten Metadaten filtern, an denen Sie interessiert sind.

In diesem Muster ist das Verzeichnis ein einzelnes Pub/Sub-Thema. Mit dem Ereignissystem können Sie jedes Mal, wenn ein Datensatz in den Stream geschrieben wird, Cloud Functions-Funktionen auslösen.

Sorgen Sie dafür, dass Ihre Abonnenten die Nachricht nicht bestätigen, damit sie für andere interessierte Funktionen verfügbar bleibt. Außerdem ist das Nachrichtenaufbewahrungsfenster für die Warteschlange begrenzt. Sie müssen also eine Cloud Functions-Funktion entwickeln, die diese Nachrichten in einem Archiv sichert. Wenn Sie archivierte Nachrichten wiedergeben möchten, können Sie mit einer Cloud Functions-Funktion archivierte Nachrichten lesen und in einem neuen Pub/Sub-Thema zur Verarbeitung veröffentlichen.

Die Datenbank mit einem Abfragedienst abfragen

Der Abfragedienst hat die Aufgabe, die Datenbank in einem festen Zeitintervall abzufragen. Dabei werden alle Datensätze angefordert, die nach einem bestimmten Zeitpunkt erstellt wurden. Sie werden nach dem Commit-Zeitstempel aufsteigend sortiert (ältester Datensatz zuerst).

Anforderungen an den Abfragedienst

Dieses Design bedeutet, dass Sie sich den letzten Zeitstempel, den der Abfragedienst gesehen hat, merken und das System für die erste Ausführung booten müssen. Zur Aufbewahrung dieses Zeitstempels können Sie ihn im Anwendungsstatus speichern, ihn in eine andere Datenbank schreiben oder das Verzeichnis nach dem neuesten Datensatz fragen und den Zeitstempel von dort analysieren.

Das Speichern des Datensatzes in einer anderen Datenbank kann den Dienst komplexer machen, denn die Abhängigkeit von einem anderen System schafft eine weitere Fehlerstelle. Es ist besser, den Zeitstempel des letzten Prozesses im lokalen Anwendungsspeicher zu belassen und das Verzeichnis nur dann abzufragen, wenn die Anwendung aus irgendeinem Grund ausfällt oder neu gestartet wird und der interne Status verloren geht.

Der Abfragedienst muss folgende Anforderungen erfüllen:

  • Festes, einheitliches Abfrageintervall.
  • Das Abfrageintervall beträgt weniger als 1 Sekunde.
  • Bootet das System beim ersten Start.
  • Fragt alle Datensätze ab, die nach dem zuvor gemerkten Zeitstempel auftreten.
  • Serialisiert jeden Datensatz in einen einzelnen Avro-Datensatz.
  • Veröffentlicht jeden Avro-Datensatz in einem Ereignisverzeichnis in Pub/Sub.
  • Bleibt untätig, bis das Abfrageintervall vorbei ist.
  • Wenn ein Fehler auftritt, wird der Versuch automatisch innerhalb des festen Zeitintervalls wiederholt.
  • Wenn ein Fehler beim Neustart des Abfragedienstes auftritt, fragt er den zuletzt aufgezeichneten Zeitstempel beim Pub/Sub-Stream ab. Wenn dies fehlschlägt, können Sie den Poller mit einem manuell konfigurierten Zeitstempel starten. Alternativ dazu können Sie den Zeitstempel aus den Cloud Storage-Archiven abrufen.

Den Abfragedienst entwickeln

Als Nächstes entwickeln Sie den Abfragedienst. Es gibt mindestens drei verschiedene Designmöglichkeiten dafür. Jede hat ihre Vor- und Nachteile.

Sie können mit Cloud Scheduler eine Cloud Functions-Funktion zum Abfragen der Datenbank planen, den Abfragedienst in einem Kubernetes-Cluster als Cronjob starten und im gewünschten Intervall planen oder einen Dienst aufbauen, der kontinuierlich in einem Kubernetes-Pod ausgeführt wird und die Datenbank mit einer festen Verzögerung abfragt, wobei der letzte verarbeitete Zeitstempel intern gespeichert und aktualisiert wird.

Da bekannt ist, dass Sie die Datenbank in einem festgelegten Zeitintervall abfragen müssen, können Sie mit Cloud Scheduler eine Cloud Functions-Funktion planen, die die Datenbank abfragt und die neuen Datensätze dann an einen Pub/Sub-Stream sendet.

Das funktioniert, hat jedoch zwei Nachteile. Erstens müssen Sie eine Möglichkeit finden, den Anwendungsstatus aufzubewahren, denn Cloud Functions-Funktionen sind definitionsgemäß zustandslos. Zum Aufbewahren des Anwendungsstatus müssen Sie eine zusätzliche Datenbank einbeziehen. Außerdem entstehen einige neue Inkonsistenzen bei der Latenz zwischen dem Schreiben von Ereignissen und der Aufnahme von Ereignissen in das Verzeichnis. Das Erzeugen und Ausführen von Cloud Functions-Funktionen kann manchmal länger dauern als erwartet. Diese Verzögerung macht sich bei kürzeren Abfrageintervallen noch stärker bemerkbar.

Wenn alle Ihre absehbaren nachgeschalteten Verarbeiter mit variabler Latenz zurechtkommen, die manchmal eine Sekunde überschreiten kann, könnte eine geplante Cloud Functions-Funktion für Ihr Systemdesign die beste Wahl sein. In diesem Fall ermittelt Ihre Cloud Functions-Funktion den letzten verarbeiteten Zeitstempel durch Abfragen des Pub/Sub-Streams oder speichert diesen Status in Cloud Storage. Dies ist nicht so kompliziert zu verwalten wie etwa Kubernetes, doch bei der Cloud Functions-Funktion muss man außerdem bedenken, dass diese zusätzlichen Abfragen nach dem Zeitstempel weitere Latenz verursachen. Damit kommt eine weitere Fehlerstelle für Ihr Abfragesystem hinzu. Wenn Sie die zusätzliche Latenz durch diese Aufrufe tolerieren können, spricht nichts gegen eine Cloud Functions-Funktion.

Eine andere Möglichkeit besteht darin, den Abfragedienst in einem Kubernetes-Cluster als Cronjob zu starten und in dem gewünschten Intervall zu planen. Leider hat diese Vorgehensweise ähnliche Nachteile wie der Einsatz von Cloud Scheduler, und weil Kubernetes hinzukommt, ist sie noch komplexer. Sie können den Job jedoch als Dienst in Kubernetes starten und ihn für ein festgelegtes Intervall warten lassen, das Sie in der Hauptereignisschleife der Anwendung steuern. Sie können den Status beibehalten und haben vollständige Kontrolle über die Abfragelatenz und die Wiederholungssemantik. Wenn Sie sich hierfür entscheiden, kommt zwar die Komplexität von Kubernetes dazu, diese kann aber mit Google Kubernetes Engine (GKE) reduziert werden. Dafür haben Sie folgende Punkte im Griff:

  • Den Status zwischen den Abfragen (letzter Zeitstempel).
  • Die Latenz zwischen Abfragen.
  • Die Semantik und die Dauer von Wiederholungsversuchen, wenn der Spanner-Lesevorgang oder der Schreibvorgang in Pub/Sub fehlschlagen.
  • Automatischer Neustart des Abfragedienstes, wenn er abstürzt bzw. unerwartet beendet wird.

Den Abfragedienst booten

Wenn Sie den Abfragedienst zum ersten Mal ausführen, werden alle für die Ausführung des ereignisgesteuerten Systems erforderlichen Komponenten eingerichtet. Dazu gehören der Pub/Sub-Stream mit dem richtigen Namen (idealerweise dem Namen der Tabelle) und der Cloud Storage-Bucket, in dem der archiver veröffentlichen kann. Nachdem alle Systemkomponenten eingerichtet wurden, liest der Abfragedienst beim Booten erstmals die Tabelle und verarbeitet alle vorhandenen Daten. Anschließend geht er zu seinem festgelegten Abfrageintervall über und übergibt den letzten verarbeiteten Commit-Zeitstempel, sodass das Abfragesystem diesen beim ersten Verarbeitungslauf verwenden kann.

Daten mit einem Verzeichnis interpretieren

Nach der Planung des Abfragedienstes und dem Abruf der neuesten Daten müssen Sie diese Daten im Verzeichnis darstellen. Da es sich um eine allgemeine Lösung handelt (dieser Abfragedienst soll für viele verschiedene Tabellen mit unterschiedlichen Schemas wiederverwendbar sein), wäre sie nicht skalierbar, wenn Sie tabellenspezifische Abfragedienste und Verzeichnisverarbeiter schreiben. Außerdem muss es möglich sein, verschiedene Verarbeiter für das Verzeichnis zu erstellen, ohne dass diese zuvor die versionierten Schemavarianten kennen müssen.

Hier einige mögliche Anwendungsfälle:

  • Langzeitarchivierung aller Transaktionen.
  • Laden von Transaktionen in ein Data Warehouse zur Analyse.
  • Laden von Transaktionen in eine NoSQL-Datenbank, um sie in maschinelle Lernmodelle einzugeben und die Antworten auf häufig gestellte Fragen möglicherweise näher am Nutzer im Cache zu speichern.

Verwenden Sie für diese Anwendungsfälle ein Serialisierungsformat wie Avro, JSON oder Protobuf. BigQuery, die Data-Warehouse-Lösung von Google, unterstützt die direkte Datenübernahme aus Avro-Dateien. Avro ist das bevorzugte Format zum Laden von Daten in BigQuery. Das Laden von Avro-Dateien hat gegenüber JSON folgende Vorteile:

  • Kürzere Ladezeiten. Die Daten können parallel eingelesen werden, auch wenn die Datenblöcke komprimiert sind.
  • Keine manuellen Eingaben oder Serialisierung erforderlich.
  • Es ist einfacher zu analysieren, da es keine inhärenten Codierungsprobleme gibt wie in anderen Formaten.

Wenn Sie Avro-Dateien in BigQuery laden, wird das Tabellenschema automatisch aus den selbstbeschreibenden Quelldaten ermittelt.

Protobuf ist eine Alternative zu Avro, aber bei diesem Anwendungsfall hat Avro zwei entscheidende Vorteile:

  • Die Datenübernahme in BigQuery wird direkt unterstützt.
  • Das Schema ist im Datenobjekt enthalten.

Durch den letzten Punkt wird es möglich, dass Verzeichnisverarbeiter die Daten abrufen und überprüfen können. Sie müssen JSON nicht deserialisieren und hoffen, dass sich das Format nicht ändert, oder eine Schema-Registry-Version für eine bestimmte Version dieses Objekts abrufen.

Aus diesen Gründen sollten Sie die Tabellen aus Spanner für jede Transaktion in ein Avro-Objekt serialisieren, bevor Sie sie in das Verzeichnis aufnehmen. Weitere Informationen finden Sie unter Eine Spanner-Tabelle in einen Avro-Datensatz umwandeln.

Die Anwendung für den Abfragedienst schreiben

Nachdem Sie sich für ein Bereitstellungsmodell und ein Serialisierungsformat entschieden haben, sollten Sie sich überlegen, in welcher Sprache Sie die Abfrageanwendung schreiben möchten. Da Sie Daten aus Spanner lesen und in Pub/Sub schreiben möchten, sind Sie auf die Sprachen beschränkt, die von den Google Cloud APIs unterstützt werden. Das sind C#, Go, Java, Node.js, PHP, Python und Ruby.

Von den derzeit von Spanner unterstützten Sprachen unterstützt Avro offiziell C#, Java, Python, PHP und Ruby. Da Sie die Latenz Ihrer Anwendung möglichst gut kontrollieren und die abgefragten Tabellen in mehreren Threads verarbeiten möchten, ist Java eine gute Wahl.

Den Ereignis-Stream verwenden

Der Abfragedienst ist die Hauptanwendung innerhalb des Dienstes, aber es gibt noch andere Empfänger des Streams. Die erste Anwendung, die Sie benötigen, ist eine Ausgabe, die den Stream abonniert und jede Nachricht in Cloud Storage archiviert. enthält. Dieses System speichert jeden Datensatz als separate Datei in einem Bucket mit demselben Namen wie die Tabelle.

Jede Stunde (oder je nach Ihrer Transaktionshäufigkeit in anderen Abständen) werden diese einzelnen Transaktionsdatensätze von einem anderen Dienst zu einer größeren Datei komprimiert. Je nach der Häufigkeit der Transaktionen und der Größe der Transaktionsdatensätze können Sie auch in Betracht ziehen, die Avro-Dateien mit den Transaktionen einzeln zu komprimieren.

Wenn alle historischen Transaktionen in Cloud Storage archiviert sind, haben Sie folgende Möglichkeiten:

  • Sie können die Transaktionsdaten für Analysen direkt in BigQuery schreiben.
  • Sie können historische Datensätze wiedergeben und damit andere Systeme trainieren oder testen.
  • Sie können den Inhalt des Systems aus dem Datensatz (in diesem Fall die Spanner-Datenbank) wiederherstellen, wenn er verloren gegangen oder beschädigt ist.
  • Sie können ein schreibgeschütztes Replikat des Verlaufs für die Berichterstellung oder Prüfung erstellen.
  • Sie können eine Version der Datenbank für Staging- oder Testumgebungen erstellen.

Die Cloud Functions-Funktion archiver erstellen

Erstellen Sie eine Cloud Functions-Funktion namens archiver, die jedes Mal ausgelöst wird, wenn eine Transaktion in den Stream aufgenommen wird. Sie müssen pro Thema (eine Tabelle in Spanner) eine neue Funktion erstellen und auslösen. Jedes Mal, wenn eine Transaktion in das konfigurierte Pub/Sub-Thema aufgenommen wird, erfasst die Cloud Functions-Funktion archiver den Avro-Datensatz und schreibt ihn in einen Cloud Storage-Bucket mit demselben Namen wie die Tabelle. Die Cloud Functions-Funktion gibt der Datei den Namen der Tabelle, erweitert um das Datum und die Uhrzeit bis auf die Millisekunde sowie vier zufällige Ziffern. Mit diesem Benennungsschema wird eine ID erstellt, die ähnlich aufgebaut ist wie eine universell eindeutige Kennung (Universally Unique Identifier, UUID).

Die Cloud Functions-Funktion bqloader erstellen

Jetzt können Sie eine neue Cloud Functions-Funktion namens bqloader erstellen, die diese Avro-Datei direkt in BigQuery lädt. Die Funktion wird ausgelöst, wenn der archiver die Datei in Cloud Storage hochlädt. Jedes Mal, wenn eine Transaktion in Cloud Storage geladen wird, fügt diese Cloud Functions-Funktion den Transaktionseintrag an die richtige BigQuery-Tabelle an. Wenn Sie die Latenz weiter reduzieren möchten, beispielsweise für Analysen oder zur Datenübergabe an Modelle für maschinelles Lernen, können Sie die Daten satzweise in BigQuery streamen. Dazu kann die Methode tabledata.insertAll verwendet werden. Bei dieser Vorgehensweise können Sie Daten ohne die Verzögerung abfragen, die durch die Ausführung eines Ladejobs entsteht. Denken Sie aber an die Kontingente für das Laden von Datensätzen in BigQuery.

Die Cloud Functions-Funktion janitor erstellen

Die Cloud Functions-Funktion archiver schreibt die Avro-Datei in einen Cloud Storage-Bucket. Als Nächstes erstellen Sie eine weitere Cloud Functions-Funktion namens janitor, mit der alle einzelnen Transaktionen für langfristige Speicherung in einer einzigen Datei komprimiert werden. janitor gibt dieser neuen Datei einen Namen, der sich aus dem Tabellennamen, dem Datum und der in den Dateien enthaltenen Zeitspanne zusammensetzt. Wenn janitor beispielsweise planmäßig jede Stunde ausgeführt wird, kann der Dateiname table1-jan_1_2019_1200-1300.tar.gz lauten. Die Cloud Functions-Funktion janitor ist nicht zwingend erforderlich, hilft jedoch, die Speicherkosten niedrig zu halten und die Cloud Storage-Buckets zu organisieren.

Die Cloud Functions-Funktion replayer erstellen

Wenn Sie die archivierten Avro-Dateien in Cloud Storage wiedergeben möchten, brauchen Sie eine andere Cloud Functions-Funktion namens replayer, die über HTTP ausgelöst wird. Die Cloud Functions-Funktion replayer entpackt die archivierten Dateien für den angeforderten Zeitraum und veröffentlicht sie sortiert in einem neuen Pub/Sub-Stream.

Diese Cloud Functions-Funktion wird über eine HTTP-POST-Anfrage ausgelöst, in der der Zeitraum angegeben wird, der wiedergegeben werden soll. Die Cloud Functions-Funktion gibt nach Ausführung den Namen des Pub/Sub-Streams zurück oder, wenn nicht alle archivierten Daten ordnungsgemäß in den neuen Stream geladen werden konnten, einen Fehlercode und eine Beschreibung.

Wenn die archivierten Dateien für eine gleichmäßige Wiedergabe zu groß werden oder die Wiedergabe für Ihren Anwendungsfall zu langsam wird, muss diese Anwendung möglicherweise komplexer gestaltet werden. Sie können die Archive in kleinere Abschnitte unterteilen oder eine andere Sprache für die Cloud Functions-Funktion replayer wählen.

Eine andere Möglichkeit ist ein Dataflow-Job, mit dem der Job in kleinere Aufgaben aufgeteilt werden kann, die parallel ausgeführt werden. Es würde den Rahmen dieses Dokuments sprengen, eine solche Implementierung zu beschreiben, aber im GitHub-Repository von Google Cloud und in der Dataflow-Dokumentation finden Sie gute Beispiele dafür.

Die Materialisierungs-Anwendung erstellen

Wenn diese Ereignisse, die jeweils eine einzelne Transaktion darstellen, im Verzeichnis vorliegen, können Sie verschiedene Arten von Diensten nahtlos in das System einbetten, ohne zusätzlichen Code schreiben oder verschiedene Anwendungsteams koordinieren zu müssen.

Diese Anwendung beispielsweise verfolgt alle Nachrichten zu einem bestimmten Thema, filtert sie nach dem Namen eines Kunden und erstellt eine materialisierte Ansicht der relevanten Daten dieses Kunden. Im Idealfall können Sie diese Anwendung dazu verwenden, Informationen über einen Nutzer abzufragen und mit sehr geringer Latenz auf diese Informationen zuzugreifen.

Wenn Sie die Daten frühzeitig analysieren, in einem schnellen Speicher ablegen und eine detaillierte Antwort zurückgeben, können Sie die Leistung Ihrer häufig ausgeführten Abfragen verbessern.

Diese Materialisierungs-Anwendung kann aus einer Cloud Functions-Funktion bestehen, die von einem Pub/Sub-Thema ausgelöst wird, die Informationen nach Kunden-ID filtert und bei Übereinstimmung die relevanten Daten in Memorystore speichert.

Fazit

Durch den Aufbau einer ereignisbasierten Systemarchitektur lassen sich neue Funktionen und Flexibilität für jedes System schaffen, das auf die Beziehung zwischen Ereignissen reagieren oder diese verstehen muss. Wenn eine deterministische Reihenfolge oder ein hoher Lesedurchsatz wichtig sind, kann der Einsatz von Spanner als Ereignislesesystem und Pub/Sub als Ereignisverzeichnis eine robuste und zuverlässige Grundlage für die ereignisbasierte Architektur bilden. Haben Sie erst einmal eine ereignisbasierte Grundlage geschaffen, können Sie die unzähligen Möglichkeiten entdecken, wie bisher komplizierte Probleme dank Cloud Functions oder Pub/Sub-Abonnements einfach gelöst werden können.

Weitere Informationen