Kapitel 4. Datenmanagement-Muster

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

Daten sind der Schlüssel für alle Anwendungen. Selbst ein einfacher Echodienst ist auf die Daten in der eingehenden Nachricht angewiesen, um eine Antwort zu senden. In diesem Kapitel dreht sich alles um Daten und ihre Verwaltung in Cloud Native Applications.

Zunächst werden wir uns auf die Datenarchitektur konzentrieren und erklären, wie Daten in Cloud-nativen Anwendungen gesammelt, verarbeitet und gespeichert werden. Dann werden wir uns mit dem Verständnis von Daten beschäftigen, indem wir sie anhand ihrer Verwendung in einer Anwendung, ihrer Struktur und ihres Umfangs in verschiedene Kategorien einteilen. Wir besprechen mögliche Speicherungs- und Verarbeitungsoptionen und wie man die beste Wahl für eine bestimmte Art von Daten trifft.

Anschließend werden wir verschiedene Muster im Zusammenhang mit Daten erläutern, wobei der Schwerpunkt auf zentralen und dezentralen Daten, Datenzusammensetzung, Caching, Management, Leistungsoptimierung, Zuverlässigkeit und Sicherheit liegt. Das Kapitel behandelt auch verschiedene Technologien, die derzeit in der Branche eingesetzt werden, um diese Entwicklungsmuster für Cloud Native Applications effektiv umzusetzen.

Dieses Wissen über Daten, Muster und Technologien hilft dir dabei, Cloud-native Anwendungen für deinen spezifischen Anwendungsfall und die Art der Daten zu entwickeln, mit denen deine Anwendungen arbeiten.

Datenarchitektur

Native Anwendungen in der Cloud sollten in der Lage sein, Daten so zu sammeln, zu speichern, zu verarbeiten und zu präsentieren, dass unsere Anwendungsfälle erfüllt werden(Abbildung 4-1).

Bei den Datenquellen handelt es sich um Cloud-native Anwendungen, die Daten wie Benutzereingaben und Sensormesswerte einspeisen. Manchmal speisen sie Daten in Datenerfassungssysteme wie z. B. Nachrichtenbroker ein oder schreiben, wenn möglich, direkt in Datenspeicher. Datenerfassungssysteme können Daten in Form von Ereignissen/Nachrichten an andere Anwendungen oder Datenspeicher weiterleiten; dadurch können wir eine zuverlässige und asynchrone Datenverarbeitung erreichen.(In Kapitel 5 erfährst du mehr über Dateneingabesysteme).

Data architecture for cloud native applications
Abbildung 4-1. Datenarchitektur für Cloud-native Anwendungen

Die Datenspeicher sind der entscheidende Teil dieser Architektur; sie speichern Daten in verschiedenen Formaten und in großem Umfang, um den Anwendungsfall zu erleichtern. Sie werden als Quelle für die Erstellung von Berichten und als Basis für Daten-APIs verwendet. In den folgenden Abschnitten gehen wir näher auf die Datenspeicher ein.

Echtzeitsysteme und Stream-Processing verarbeiten Ereignisse im laufenden Betrieb und liefern nützliche Erkenntnisse für den jeweiligen Anwendungsfall sowie Warnungen und Benachrichtigungen, wenn sie eintreten. Kapitel 6 behandelt diese Systeme im Detail. Stapelverarbeitungssysteme verarbeiten Daten aus Datenquellen in Stapeln und schreiben die verarbeiteten Ergebnisse zurück in die Datenspeicher, damit sie für Berichte verwendet oder über APIs zugänglich gemacht werden können. In diesen Fällen kann das Verarbeitungssystem Daten aus einer Art von Speicher lesen und in einen anderen schreiben, z. B. aus einem Dateisystem lesen und in eine relationale Datenbank schreiben. Die Stapelverarbeitung von Cloud Native Data ähnelt der herkömmlichen Stapelverarbeitung von Daten, daher gehen wir hier nicht näher darauf ein.

Genau wie Cloud Native Microservices Eigenschaften wie Skalierbarkeit, Belastbarkeit und Verwaltbarkeit aufweisen, haben auch Cloud Native Daten ihre eigenen, einzigartigen Eigenschaften, die sich von traditionellen Datenverarbeitungspraktiken unterscheiden. Am wichtigsten ist, dass Cloud Native-Daten in vielen Formen, in einer Vielzahl von Datenformaten und Datenspeichern gespeichert werden können. Es wird nicht erwartet, dass sie ein festes Schema beibehalten, und es wird empfohlen, doppelte Daten zu haben, um die Verfügbarkeit und Leistung gegenüber der Konsistenz zu verbessern. Außerdem sollten in nativen Cloud-Anwendungen nicht mehrere Dienste auf dieselbe Datenbank zugreifen. Stattdessen sollten sie die jeweiligen Service-APIs aufrufen, die den Datenspeicher besitzen, um auf die Daten zuzugreifen. All dies sorgt für eine Trennung der Interessen und ermöglicht die Skalierung von Cloud Native-Daten.

Arten und Formen von Daten

Daten haben in ihren vielfältigen Formen einen großen Einfluss auf Anwendungen - ob cloud-nativ oder nicht. In diesem Abschnitt wird erörtert, wie Daten die Ausführung einer Anwendung verändern, welche Formate diese Daten haben und wie Daten am besten übertragen und gespeichert werden können.

Das Verhalten der Anwendung wird von den folgenden drei Haupttypen von Daten beeinflusst:

Eingangsdaten
als Teil der Eingabenachricht durch den Nutzer oder Client gesendet. Meistens handelt es sich bei diesen Daten um JSON- oder XML-Nachrichten, aber auch binäre Formate wie gRPC und Thrift werden immer beliebter.
Konfigurationsdaten
von der Umgebung als Variablen zur Verfügung gestellt. XML wird schon lange als Konfigurationssprache verwendet, und inzwischen sind YAML-Konfigurationen zum De-facto-Standard für Cloud Native Applications geworden.
Staatliche Daten
Die von der Anwendung selbst gespeicherten Daten zu ihrem Status basieren auf allen Meldungen und Ereignissen, die vor dem aktuellen Zeitpunkt eingetreten sind. Indem die Zustandsdaten persistiert und beim Start geladen werden, kann die Anwendung nach dem Neustart nahtlos ihre Funktionalität wieder aufnehmen.

Anwendungen, die nur von Eingabe- und Konfigurationsdaten (Config) abhängen, werden als zustandslose Anwendungen bezeichnet. Diese Anwendungen sind relativ einfach zu implementieren und zu skalieren, da ihr Ausfall oder Neustart fast keine Auswirkungen auf ihre Ausführung hat. Im Gegensatz dazu sind Anwendungen, die von Eingabe-, Konfigurations- und Zustandsdaten abhängen - zustandsabhängigeAnwendungen- viel komplexer zu implementieren und zu skalieren. Der Zustand der Anwendung wird in Datenspeichern gespeichert, so dass es bei Anwendungsausfällen zu partiellen Schreibvorgängen kommen kann, die den Zustand der Anwendung beschädigen, was zu einer fehlerhaften Ausführung der Anwendung führen kann.

Cloud-native Anwendungen fallen in die Kategorien zustandsabhängig und zustandslos. In Kapitel 3 wurden zustandslose Anwendungen behandelt. Dieses Kapitel konzentriert sich auf zustandsabhängige Anwendungen.

Cloud-native Anwendungen nutzen verschiedene Formen von Daten, die im Allgemeinen in die folgenden drei Kategorien eingeteilt werden:

Strukturierte Daten
Kann in ein vordefiniertes Schema passen. Die Daten in einem typischen Benutzerregistrierungsformular können zum Beispiel bequem in einer relationalen Datenbank gespeichert werden.
Semi-strukturierte Daten
Hat irgendeine Form von Struktur. Zum Beispiel kann jedes Feld in einem Dateneintrag einen entsprechenden Schlüssel oder Namen haben, mit dem wir darauf verweisen können, aber wenn wir alle Einträge nehmen, gibt es keine Garantie, dass jeder Eintrag die gleiche Anzahl von Feldern oder sogar gemeinsame Schlüssel hat. Diese Daten können leicht durch die Formate JSON, XML und YAML dargestellt werden.
Unstrukturierte Daten
Enthält keine sinnvollen Felder. Bilder, Videos und rohe Textinhalte sind Beispiele dafür. Normalerweise werden diese Daten gespeichert, ohne dass ihr Inhalt bekannt ist.

Datenspeicher

Wir müssen den Typ des Datenspeichers für Cloud-native Daten je nach Anwendungsfall auswählen. Verschiedene Anwendungsfälle verwenden unterschiedliche Datentypen (strukturiert, halbstrukturiert oder unstrukturiert) und haben unterschiedliche Anforderungen an Skalierbarkeit und Verfügbarkeit. Mit den verschiedenen verfügbaren Speicheroptionen bieten unterschiedliche Datenspeicher unterschiedliche Eigenschaften, z. B. bietet ein Speicher eine hohe Leistung und ein anderer eine hohe Skalierbarkeit. Manchmal verwenden wir sogar mehr als einen Datenspeicher gleichzeitig, um verschiedene Eigenschaften zu erreichen. In diesem Abschnitt gehen wir auf gängige Arten von Datenspeichern ein und zeigen, wann und wie sie in Cloud Native Applications eingesetzt werden können.

Relationale Datenbanken

Relationale Datenbanken sind ideal für die Speicherung strukturierter Daten, die ein vordefiniertes Schema haben. Diese Datenbanken verwenden die Structured Query Language (SQL) für die Verarbeitung, Speicherung und den Zugriff auf Daten. Sie folgen auch dem Prinzip der Schemadefinition on write: Das Datenschema wird definiert, bevor die Daten in die Datenbank geschrieben werden.

Relationale Datenbanken können Daten optimal speichern und abrufen, indem sie Datenbankindizes und Normalisierung verwenden. Da diese Datenbanken die Eigenschaften Atomarität, Konsistenz, Isolation und Haltbarkeit (ACID) unterstützen, können sie auch Transaktionsgarantien bieten. Atomarität garantiert, dass alle Operationen innerhalb einer Transaktion als eine Einheit ausgeführt werden; Konsistenz stellt sicher, dass die Daten vor und nach der Transaktion konsistent sind; Isolation macht den Zwischenzustand einer Transaktion für andere Transaktionen unsichtbar; und schließlich garantiert Haltbarkeit, dass die Daten nach einer erfolgreichen Transaktion auch bei einem Systemausfall erhalten bleiben. All diese Eigenschaften machen relationale Datenbanken ideal für die Implementierung geschäftskritischer Finanzanwendungen.

Relationale Datenbanken funktionieren nicht gut mit halbstrukturierten Daten. Wenn wir zum Beispiel Produktkatalogdaten für eine E-Commerce-Website speichern und die ursprüngliche Eingabe Produktdetails, Preise, einige Bilder und Bewertungen enthält, können wir nicht alle diese Daten in einem relationalen Speicher ablegen. In diesem Fall müssen wir nur die wichtigsten und gebräuchlichsten Felder wie Produkt-ID, Name, Details und Preis extrahieren und in einer relationalen Datenbank speichern, während wir die Liste der Produktbewertungen in NoSQL und die Bilder in einem Dateisystem speichern. Dieser Ansatz kann jedoch zu Leistungseinbußen führen, da beim Abrufen aller Daten mehrfach nachgeschaut werden muss. In solchen Fällen empfehlen wir, kritische unstrukturierte und halbstrukturierte Datenfelder wie die Produktvorschaubilder als Blob oder Text in einem relationalen Datenspeicher zu speichern, um die Leseleistung zu verbessern. Berücksichtige bei diesem Ansatz immer die Kosten und den Speicherplatzbedarf von relationalen Datenbanken.

Relationale Datenbanken sind eine gute Option für die Speicherung von Cloud-nativen Anwendungsdaten. Wir empfehlen die Verwendung einer relationalen Datenbank pro Microservice, da dies die Bereitstellung und Skalierung der Daten zusammen mit dem Microservice als eine einzige Bereitstellungseinheit erleichtert. Es ist wichtig, sich daran zu erinnern, dass relationale Datenbanken nicht skalierbar sind. Was die Skalierung angeht, können sie nur eine primäre/sekundäre Architektur unterstützen, bei der ein Knoten für Schreibvorgänge und mehrere Worker-Knoten für Lesevorgänge zuständig sind.

Daher empfehlen wir die Verwendung relationaler Datenbanken in nativen Cloud-Anwendungen, wenn die Anzahl der Datensätze im Speicher nie die Grenze überschreitet, die die Datenbank effizient verarbeiten kann. Wenn absehbar ist, dass die Daten ständig wachsen werden, wie z. B. bei der Anzahl der gespeicherten Bestellungen, Protokolle oder Benachrichtigungen, dann müssen wir möglicherweise Datenskalierungsmuster für relationale Datenspeicher einsetzen, die wir später in diesem Kapitel besprechen, oder wir sollten nach anderen Alternativen suchen.

NoSQL-Datenbanken

Der Begriff NoSQL wird meist missverstanden als nicht SQL. Vielmehr ist es besser, ihn als " nicht nur SQL" zu erklären. Das liegt daran, dass diese Datenbanken immer noch eine gute SQL-ähnliche Abfrageunterstützung und ein entsprechendes Verhalten haben, zusammen mit vielen anderen Vorteilen, wie z.B. Skalierbarkeit und der Möglichkeit, halbstrukturierte Daten zu speichern und zu verarbeiten. NoSQL-Datenbanken folgen dem Prinzip des Schemas beim Lesen: Das Schema der Daten wird erst zum Zeitpunkt des Zugriffs auf die Daten für die Verarbeitung definiert und nicht, wenn sie auf die Festplatte geschrieben werden.

Diese Datenbanken eignen sich am besten für den Umgang mit Big Data, da sie auf Skalierbarkeit und Leistung ausgelegt sind. Da NoSQL-Datenbanken dezentralisiert sind, können wir sie für mehrere Cloud-Anwendungen nutzen. Um die Leistung zu optimieren, sind die in NoSQL-Datenbanken gespeicherten Daten normalerweise nicht normalisiert und können redundante Felder enthalten. Wenn die Daten normalisiert sind, müssen beim Abruf von Daten Tabellen-Joins durchgeführt werden, was aufgrund der verteilten Natur dieser Datenbanken zeitaufwändig sein kann. Außerdem unterstützen nur wenige NoSQL-Speicher Transaktionen, was ihre Leistung und Skalierbarkeit beeinträchtigt. Daher ist es generell nicht empfehlenswert, Daten in NoSQL-Speichern zu speichern, die Transaktionsgarantien benötigen.

Der Einsatz von NoSQL-Speichern in Cloud-nativen Anwendungen ist unterschiedlich, da es verschiedene Arten von NoSQL-Speichern gibt und sie im Gegensatz zu relationalen Datenbanken keine Verhaltensallgemeinheiten aufweisen. Diese NoSQL-Speicher können nach der Art der Datenspeicherung und den Konsistenz- und Verfügbarkeitsgarantien, die sie bieten, kategorisiert werden.

Einige gängige NoSQL-Speicher, die nach der Art der Datenspeicherung kategorisiert sind, sind die folgenden:

Schlüssel-Wert-Speicher
Diese enthält Datensätze als Schlüssel-Wert-Paare. Wir können dies für die Speicherung von Anmeldesitzungsinformationen auf der Grundlage von Sitzungs-IDs verwenden. Diese Art von Speichern wird häufig zum Zwischenspeichern von Daten verwendet. Redis ist ein beliebter Open-Source-Schlüsselwertdatenspeicher. Memcached und Ehcache sind weitere beliebte Optionen.
Säulenladen

Diese speichert mehrere Schlüssel- (Spalten-) und Wertepaare in jeder ihrer Zeilen, wie in Abbildung 4-2 dargestellt. Diese Speicher sind ein gutes Beispiel für Schema on Read: Während der Schreibphase können wir eine beliebige Anzahl von Spalten schreiben, und beim Abruf der Daten können wir nur die Spalten angeben, die wir verarbeiten wollen. Der am weitesten verbreitete Spaltenspeicher ist Apache Cassandra. Für diejenigen, die Big Data und die Apache Hadoop-Infrastruktur nutzen, kann Apache HBase eine Option sein, da es Teil des Hadoop-Ökosystems ist.

Column store
Abbildung 4-2. Säulenlager
Dokumentenspeicher
Diese kann halbstrukturierte Daten wie JSON und XML Dokumente speichern. Außerdem können wir so gespeicherte Dokumente mit Hilfe von JSON- und XML-Pfadausdrücken verarbeiten. Diese Datenspeicher sind beliebt, da sie JSON- und XML-Nachrichten speichern können, die in der Regel von Frontend-Anwendungen und APIs zur Kommunikation verwendet werden. MongoDB, Apache CouchDB und CouchBase sind beliebte Optionen für die Speicherung von JSON-Dokumenten.
Grafikspeicher
Diese speichern Daten als Knoten und verwenden Kanten, um die Beziehungen zwischen den Datenknoten darzustellen. Diese Speicher sind mehrdimensional und eignen sich für den Aufbau und die Abfrage von Netzwerken, z. B. von Freundesnetzwerken in sozialen Medien und Transaktionsnetzwerken zur Betrugserkennung. Neo4j, der beliebteste Graphdatenspeicher, wird von führenden Unternehmen der Branche intensiv genutzt.

Viele andere Arten von NoSQL-Speichern, darunter Objektspeicher und Zeitreihendatenspeicher, können helfen, anwendungsspezifische Spezialdaten zu speichern und abzufragen. Einige Speicher haben auch ein Multimodell-Verhalten; sie können in mehrere der oben genannten Kategorien fallen. Amazon DynamoDB kann zum Beispiel als Schlüsselwert- und Dokumentenspeicher fungieren und Azure Cosmos DB als Schlüsselwert-, Spalten-, Dokumenten- und Graphenspeicher.

Da NoSQL-Speicher verteilt sind, müssen sie das CAP-Theorem einhalten. CAP steht für Konsistenz, Verfügbarkeit und Partitionstoleranz. Dieses Theorem besagt, dass eine verteilte Anwendung entweder volle Verfügbarkeit oder Konsistenz bieten kann; wir können nicht beides erreichen und gleichzeitig Netzwerkpartitionstoleranz bieten. Dabei bedeutet Verfügbarkeit, dass das System auch dann voll funktionsfähig ist, wenn einige seiner Knoten ausfallen, Konsistenz bedeutet, dass eine Aktualisierung/Änderung in einem Knoten sofort an andere Knoten weitergegeben wird, und Partitionstoleranz bedeutet, dass das System auch dann noch funktioniert, wenn einige Knoten keine Verbindung zueinander herstellen können. Einige Läden geben der Konsistenz den Vorrang vor der Verfügbarkeit, während andere die Verfügbarkeit über die Konsistenz stellen.

Angenommen, wir müssen die Anzahl der Bürgerinnen und Bürger in einem Land erfassen und melden, und wenn die neuesten Daten bei der Berechnung fehlen, wird das Endergebnis nicht wesentlich verfälscht. Wir können einen Datenspeicher verwenden, der die Verfügbarkeit begünstigt. Wenn wir hingegen Transaktionen für Geschäftszwecke verfolgen müssen, müssen wir einen Datenspeicher wählen, der die Konsistenz fördert.

Tabelle 4-1 kategorisiert NoSQL-Datenspeicher in Bezug auf Konsistenz und Verfügbarkeit .

Tabelle 4-1. NoSQL-Datenspeicher, die Konsistenz und Verfügbarkeit bevorzugen
Konsistenz bevorzugen Verfügbarkeit bevorzugen
Schlüssel-Wert-Läden Redis, Memcached DynamoDB, Voldemort
Säulenläden Google Cloud Bigtable, Apache HBase Apache Cassandra
Dokumentenspeicher MongoDB, Terrastore CouchDB, SimpleDB
Grafische Läden Azure Cosmos DB Neo4j

Obwohl einige die Konsistenz und andere die Verfügbarkeit bevorzugen, können auch andere NoSQL-Datenspeicher (wie Cassandra und DynamoDB) beides bieten. In Cassandra können wir zum Beispiel Konsistenzstufen wie One, Quorum oder All festlegen. Wenn die Konsistenzstufe auf Eins eingestellt ist, werden die Daten nur auf einem Knoten im Cluster gelesen/geschrieben und bieten so volle Verfügbarkeit mit eventueller Konsistenz. Während der eventuellen Konsistenz werden die Daten schließlich an andere Knoten weitergegeben, und Lesevorgänge können während dieser Zeit veraltet sein. Bei der Einstellung All hingegen werden die Daten von allen Knoten gelesen/geschrieben, bevor der Vorgang erfolgreich ist, was zu einer starken Konsistenz mit Leistungseinbußen führt. Bei der Verwendung von Quorum werden die Daten jedoch nur von 51% der Knoten gelesen/geschrieben. Auf diese Weise können wir sicherstellen, dass die neueste Aktualisierung auf mindestens einem Knoten verfügbar ist, was sowohl Konsistenz als auch Verfügbarkeit bei minimalem Leistungsaufwand gewährleistet.

Daher empfehlen wir, dass du die Art der Daten und ihre Verwendungszwecke innerhalb von Cloud-nativen Anwendungen verstehst, bevor du den richtigen NoSQL-Datenspeicher auswählst. Erinnere dich daran, dass das Datenformat sowie die Anforderungen an die Konsistenz und Verfügbarkeit der Daten deine Wahl des Datenspeichers beeinflussen können.

Dateisystem Speicherung

DieSpeicherung im Dateisystem eignet sich am besten für die Speicherung unstrukturierter Daten in cloudbasierten Anwendungen. Anders als NoSQL-Speicher versucht er nicht, die Daten zu verstehen, sondern optimiert lediglich die Speicherung und den Abruf der Daten. Wir können die Speicherung im Dateisystem auch nutzen, um große Anwendungsdaten als Cache zu speichern, da dies billiger ist als das wiederholte Abrufen von Daten über das Netzwerk.

Dies ist zwar die billigste Option, aber möglicherweise keine optimale Lösung, wenn es um die Speicherung von Text oder halbstrukturierten Daten geht, denn dann müssen wir bei der Suche nach einem einzigen Dateneintrag mehrere Dateien laden. In diesen Fällen empfehlen wir die Verwendung von Indexierungssystemen wie Apache Solr oder Elasticsearch, um die Suche zu erleichtern.

Wenn Daten in großem Umfang gespeichert werden müssen, können verteilte Dateisysteme verwendet werden. Die bekannteste Open-Source-Option ist das Hadoop Distributed File System (HDFS), und beliebte Cloud-Optionen sind Amazon Simple Storage Service (S3), Azure Storage Services und Google Cloud Storage.

Zusammenfassung des Datenspeichers

Wir haben drei Arten von Datenspeichern besprochen: relationale, NoSQL- und Dateisysteme. Cloud-native Anwendungen sollten relationale Datenspeicher verwenden, wenn sie Transaktionsgarantien benötigen und wenn die Daten eng mit der Anwendung verbunden sein müssen.

Wenn Daten halbstrukturierte oder unstrukturierte Felder enthalten, können sie getrennt und in NoSQL- oder Dateisystem-Speichern gespeichert werden, um Skalierbarkeit zu erreichen und gleichzeitig Transaktionsgarantien zu erhalten. Die Anwendungen können sich für die Speicherung in NoSQL entscheiden, wenn die Datenmenge extrem groß ist, eine Abfragefunktion benötigt wird, die Daten halbstrukturiert sind oder der Datenspeicher so spezialisiert ist, dass er den spezifischen Anwendungsfall, wie z. B. die Graphenverarbeitung, bewältigen kann.

In allen anderen Fällen empfehlen wir, die Daten in Dateisystemen zu speichern, da diese für die Speicherung und den Abruf von Daten optimiert sind, ohne deren Inhalt zu verarbeiten. Als Nächstes werden wir uns ansehen, wie diese Daten bereitgestellt, verwaltet und von Cloud-nativen Anwendungen gemeinsam genutzt werden können.

Datenmanagement

Nachdem wir nun die Arten von Daten und entsprechenden Datenspeichern für die Entwicklung von Cloud Native Applications kennengelernt haben, geht es in diesem Abschnitt darum, wie deine Daten und Datenspeicher bereitgestellt, verwaltet und von diesen Anwendungen gemeinsam genutzt werden können. Die Daten können zentral, dezentral oder in einer Mischform verwaltet werden. Wir werden uns im Folgenden mit jeder dieser Optionen näher befassen.

Zentrales Datenmanagement

Zentralisierte Datenverwaltung ist die häufigste Art in traditionellen datenzentrierten Anwendungen. Bei diesem Ansatz werden alle Daten in einer einzigen Datenbank gespeichert, und mehrere Komponenten der Anwendung können zur Verarbeitung auf die Daten zugreifen(Abbildung 4-3).

Centralized data management in a traditional data-centric application
Abbildung 4-3. Zentralisierte Datenverwaltung in einer traditionellen datenzentrierten Anwendung

Dieser Ansatz hat mehrere Vorteile: So können die Daten in diesen Datenbanktabellen normalisiert werden, was eine hohe Datenkonsistenz gewährleistet. Da die Komponenten auf alle Tabellen zugreifen können, bietet die zentrale Speicherung der Daten außerdem die Möglichkeit, gespeicherte Prozeduren über mehrere Tabellen hinweg auszuführen und die Ergebnisse schneller abzurufen. Auf der anderen Seite führt dies zu einer engen Kopplung zwischen den Anwendungen und behindert die Möglichkeit, die Anwendungen unabhängig voneinander weiterzuentwickeln. Daher wird es als ein Antipattern bei der Entwicklung nativer Cloud-Anwendungen angesehen.

Dezentrales Datenmanagement

Um die Probleme von mit der zentralen Datenverwaltung zu überwinden, kann jede unabhängige funktionale Komponente als Microservice modelliert werden, der über separate Datenspeicher verfügt, die nur für die jeweilige Komponente gelten. Dieser dezentrale Datenverwaltungsansatz, der in Abbildung 4-4 dargestellt ist, ermöglicht es uns, Microservices unabhängig zu skalieren, ohne andere Microservices zu beeinträchtigen.

Bei diesen Datenbanken gibt es keine Kopplung, die Änderungen risikoreicher und schwieriger machen kann. Auch wenn die Eigentümer der Anwendung weniger Freiheiten bei der Verwaltung oder Weiterentwicklung der Daten haben, löst die Trennung der Daten in jedem Microservice, so dass sie von den jeweiligen Teams/Eigentümern verwaltet werden, nicht nur die Probleme der Datenverwaltung und des Datenbesitzes, sondern verbessert auch die Entwicklungszeit für die Implementierung neuer Funktionen und die Release-Zyklen.

Decentralized data management
Abbildung 4-4. Dezentrales Datenmanagement

Die dezentrale Datenverwaltung ermöglicht es den Diensten, den für ihren Anwendungsfall am besten geeigneten Datenspeicher zu wählen. Ein Zahlungsdienst kann zum Beispiel eine relationale Datenbank verwenden, um Transaktionen durchzuführen, während ein Anfragedienst einen Dokumentenspeicher verwenden kann, um die Details der Anfrage zu speichern, und ein Einkaufswagendienst kann einen verteilten Key-Value-Speicher verwenden, um die vom Kunden ausgewählten Artikel zu speichern.

Hybrides Datenmanagement

Neben den Vorteilen einer einzigen Datenbank, die wir im vorangegangenen Abschnitt erläutert haben, gibt es noch weitere betriebliche Vorteile, die sie bieten kann. Zum Beispiel hilft sie bei der Einhaltung moderner Datenschutzgesetze und erleichtert die Durchsetzung von Sicherheitsvorschriften, da die Daten an einem zentralen Ort gespeichert werden. Daher ist es ratsam, alle Kundendaten über einige wenige Microservices in einem gesicherten, abgegrenzten Kontext zu verwalten und die Verantwortung für die Daten einem oder wenigen gut ausgebildeten Teams zu übertragen, die die Datenschutzrichtlinien anwenden .

Einer der Nachteile der dezentralen Datenverwaltung sind jedoch die Kosten für den Betrieb separater Datenspeicher für jeden Dienst. Daher können wir für einige kleine und mittlere Unternehmen einen hybriden Datenmanagementansatz verwenden(Abbildung 4-5). So können sich mehrere Microservices dieselbe Datenbank teilen, vorausgesetzt, sie werden von demselben Team verwaltet und befinden sich in demselben begrenzten Kontext.

Bei der hybriden Datenverwaltung müssen wir jedoch darauf achten, dass unsere Dienste nicht direkt auf Tabellen zugreifen, die anderen Diensten gehören. Andernfalls wird das System noch komplexer und es wird schwierig, die Daten in Zukunft in mehrere Datenbanken aufzuteilen.

Hybrid data management
Abbildung 4-5. Hybrides Datenmanagement

Zusammenfassung der Datenverwaltung

In diesem Abschnitt haben wir uns angesehen, wie Cloud Native-Anwendungen als unabhängige Microservices modelliert werden und wie wir Skalierbarkeit, Wartbarkeit und Sicherheit erreichen, indem wir ausschließlich separate Datenspeicher für jeden Microservice verwenden(Abbildung 4-6).

Cloud native applications, depicted at right, have a dedicated data store for each microservice.
Abbildung 4-6. Cloud-native Anwendungen, die rechts abgebildet sind, haben einen eigenen Datenspeicher für jeden Microservice.

Wir haben gesehen, wie Anwendungen über klar definierte APIs miteinander kommunizieren, und wir können diesen Ansatz nutzen, um Daten von den jeweiligen Anwendungen abzurufen, ohne direkt auf deren Datenspeicher zuzugreifen.

Nachdem wir uns nun mit den Datentypen und -formaten sowie den Speicher- und Verwaltungsoptionen beschäftigt haben, wollen wir uns nun mit den datenbezogenen Mustern befassen, die wir bei der Entwicklung unserer nativen Cloud-Anwendungen anwenden können. Die Datenverwaltungsmuster sind ein guter Weg, um zu verstehen, wie man Daten im Hinblick auf Datenzusammensetzung, Skalierbarkeit, Leistungsoptimierung, Zuverlässigkeit und Sicherheit besser handhaben kann. Im Folgenden werden relevante Datenmanagementmuster im Detail besprochen, einschließlich ihrer Verwendung, realer Anwendungsfälle, Überlegungen und verwandter Muster.

Muster für die Datenzusammensetzung

In diesem Abschnitt werden Möglichkeiten beschrieben, wie Daten auf sinnvolle Weise geteilt und kombiniert werden können, um Cloud Native-Anwendungen effizient zu erstellen. Betrachten wir eine einfache Cloud Native-Anwendung und ihren Datenspeicher, wie in Abbildung 4-7 dargestellt. Hier ist der Microservice der Anwendung Eigentümer der Daten in seinem Datenspeicher.

Basic cloud native microservice
Abbildung 4-7. Grundlegender Cloud Native Microservice

Wenn der Dienst stark ausgelastet ist, kann es aufgrund der längeren Datenabrufzeit zu einer hohen Latenz kommen. Dies kann durch den Einsatz eines Cache (Abbildung 4-8) gemildert werden. Dadurch wird die Datenbank weniger belastet, wenn mehrere Leseanfragen auftreten, und die Gesamtleistung des Dienstes wird verbessert. Weitere Informationen zu Caching-Mustern und anderen Techniken zur Leistungsoptimierung werden später in diesem Kapitel ausführlich behandelt.

Cloud native microservice with cache
Abbildung 4-8. Cloud-nativer Microservice mit Cache

Wenn die Funktionalität des Dienstes komplexer wird, kann der Dienst in kleinere Microservices aufgeteilt werden(Abbildung 4-9). In dieser Phase werden auch die relevanten Daten aufgeteilt und zusammen mit den neuen Diensten verschoben, denn es ist nicht empfehlenswert, dass mehrere Dienste dieselben Daten gemeinsam nutzen.

Segregation of microservice by functionality
Abbildung 4-9. Aufteilung der Microservices nach Funktionalität

Manchmal ist es nicht einfach, die Daten in zwei Teile aufzuteilen, und wir brauchen eine alternative Option, um Daten auf sichere und wiederverwendbare Weise zu teilen. Das folgende Data Service Pattern erklärt im Detail, wie dies gehandhabt werden kann.

Datendienst-Muster

Das Datendienstmuster stellt die Daten in der Datenbank als Dienst zur Verfügung, der als Datendienst bezeichnet wird. Der Datendienst wird zum Eigentümer und ist für das Hinzufügen und Entfernen von Daten aus dem Datenspeicher verantwortlich. Der Dienst kann einfache Abfragen durchführen oder sogar komplexe Operationen kapseln, wenn er Antworten auf Datenanfragen erstellt.

Wie es funktioniert

Wenn wir Daten als Datendienst bereitstellen, wie in Abbildung 4-10 dargestellt, haben wir mehr Kontrolle über diese Daten. So können wir Daten in verschiedenen Zusammensetzungen für verschiedene Clients bereitstellen, Sicherheit anwenden und eine prioritätsbasierte Drosselung durchsetzen, die es nur kritischen Diensten erlaubt, in Situationen mit Ressourcenbeschränkungen wie Lastspitzen oder Systemausfällen auf die Daten zuzugreifen .

Diese Datendienste können einfache Lese- und Schreibvorgänge in einer Datenbank durchführen oder sogar komplexe Logik wie die Verknüpfung mehrerer Tabellen oder die Ausführung von gespeicherten Prozeduren, um Antworten viel effizienter zu erstellen. Diese Datendienste können auch das Caching nutzen, um ihre Leseleistung zu verbessern.

Data Service pattern
Abbildung 4-10. Datendienst-Muster

Wie es in der Praxis verwendet wird

Dieses Muster kann verwendet werden, wenn wir den Zugriff auf Daten ermöglichen müssen, die nicht zu einem einzelnen Microservice gehören, oder wenn wir alte/eigene Datenspeicher für andere Cloud-native Anwendungen abstrahieren müssen.

Erlaube mehreren Microservices den Zugriff auf dieselben Daten

Wir können dieses Muster verwenden, wenn die Daten keinem bestimmten Microservice gehören; kein Microservice ist der rechtmäßige Eigentümer dieser Daten, aber mehrere Microservices sind für ihren Betrieb davon abhängig. In solchen Fällen sollten die gemeinsamen Daten als unabhängiger Datendienst bereitgestellt werden, damit alle abhängigen Anwendungen über APIs auf die Daten zugreifen können.

Ein Beispiel: Ein E-Commerce-System hat Microservices für Bestellungen und Produktdetails, die auf Rabattdaten zugreifen müssen. Da der Rabatt nicht zu einem dieser Microservices gehört, sollte ein separater Dienst erstellt werden, der die Rabattdaten bereitstellt. Nun sollten sowohl der Bestell- als auch der Produktdetail-Microservice über APIs auf den neuen Rabattdatendienst zugreifen, um Informationen zu erhalten.

Abstrakte Legacy-/Proprietäre Datenspeicher freilegen

Wir können dieses Muster auch verwenden, um alte lokale oder proprietäre Datenspeicher für andere Cloud-native Anwendungen zugänglich zu machen. Stellen wir uns vor, dass wir eine Legacy-Datenbank haben, in der alle Geschäftsvorgänge für unsere proprietäre lokale Anwendung gespeichert sind. Wenn unsere nativen Cloud-Anwendungen auf diese Daten zugreifen sollen, müssen wir den C#-Datenbanktreiber verwenden und sicherstellen, dass alle Anwendungen die Tabellen und die Struktur der Datenbank kennen, um auf die Daten zugreifen zu können.

Es ist vielleicht keine gute Idee, direkt über den Treiber auf die Datenbank zuzugreifen, da wir dann gezwungen sind, alle unsere Cloud-nativen Anwendungen in C# zu schreiben, und alle unsere Anwendungen sollten auch das Wissen über die Tabelle einbetten. Stattdessen können wir einen einzelnen Datenservice erstellen, der die alte Datenbank ansteuert und die Daten über klar definierte APIs zugänglich macht. So können andere Cloud-native Anwendungen über APIs auf die Daten zugreifen und sich von der zugrunde liegenden Datenbanktabelle und Programmiersprache abkoppeln. Außerdem können wir so die Datenbank in Zukunft auf eine andere migrieren, ohne dass die Dienste, die von dem Datenservice abhängig sind, beeinträchtigt werden.

Überlegungen

Bei der Entwicklung von Cloud-nativen Anwendungen ist der Zugriff auf dieselben Daten über mehrere Microservices nicht empfehlenswert. Dies führt zu einer engen Kopplung zwischen den Microservices und verhindert, dass die Microservices skalieren und sich eigenständig weiterentwickeln können. Das Data Service Pattern kann helfen, die Kopplung zu reduzieren, indem es verwaltete APIs für den Datenzugriff bereitstellt.

Dieses Muster sollte nicht verwendet werden, wenn die Daten eindeutig einem bestehenden Microservice zugeordnet werden können, da die Einführung unnötiger Microservices zu einer zusätzlichen Komplexität der Verwaltung führt.

Muster für zusammengesetzte Datendienste

Das Composite-Data-Services-Muster kombiniert Daten aus mehreren Datendiensten und führt bei Bedarf eine komplexe Aggregation durch, um eine umfassendere und übersichtlichere Antwort zu geben. Dieses Muster wird auch als Server-Side Mashup-Muster bezeichnet, da die Datenzusammenstellung beim Dienst und nicht beim Datenkonsumenten erfolgt.

Wie es funktioniert

Dieses Muster, das dem Service-Orchestrierungsmuster aus Kapitel 3 ähnelt, kombiniert Daten aus verschiedenen Diensten und einem eigenen Datenspeicher zu einem zusammengesetzten Datenservice. Dieses Muster macht nicht nur mehrere Microservices für die Datenkomposition überflüssig, sondern ermöglicht auch die Zwischenspeicherung der kombinierten Daten zur Verbesserung der Leistung(Abbildung 4-11).

Composite Data Services pattern
Abbildung 4-11. Muster für zusammengesetzte Datendienste

Wie es in der Praxis verwendet wird

Dieses Muster kann verwendet werden, wenn wir mehrere Microservices, die dieselbe Datenkomposition wiederholen, eliminieren müssen. Bei feingranularen Datendiensten müssen die Kunden mehrere Dienste abfragen, um die gewünschten Daten zu erstellen. Wir können dieses Muster nutzen, um doppelte Arbeit der Kunden zu reduzieren und sie in einem gemeinsamen Dienst zusammenzufassen.

Nehmen wir ein E-Commerce-System, das den Produktbestand berechnet, indem es verschiedene Datendienste der verschiedenen Fulfillment-Stores zusammenfasst. In diesem Fall kann die Einführung eines gemeinsamen Dienstes, der die Daten aller Fulfillment-Services zusammenführt, von Vorteil sein, da dadurch doppelte Arbeit vermieden wird, die Komplexität der einzelnen Clients reduziert wird und die zusammengesetzten Datendienste sich weiterentwickeln können, ohne die Clients zu behindern.

Wenn solche Daten im Composite Data Service zwischengespeichert werden, kann auch die Antwortzeit für Bestandsinformationen verbessert werden. Das liegt daran, dass die meisten Microservices in einem bestimmten Zeitrahmen auf denselben Datensatz zugreifen und das Caching ihre Leseleistung drastisch verbessern kann.

Überlegungen

Verwende dieses Muster nur, wenn die Konsolidierung generisch genug ist und andere Microservices die konsolidierten Daten wiederverwenden können. Wir raten davon ab, unnötige Schichten von Diensten einzuführen, wenn sie keine sinnvollen Datenkompositionen liefern, die wiederverwendet werden können. Wäge die Vorteile der Wiederverwendbarkeit und der Einfachheit der Clients gegen die zusätzliche Latenz und Verwaltungskomplexität ab, die durch die Serviceschichten entstehen .

Client-seitiges Mashup-Muster

Beim Client-Side-Mashup-Muster werden Daten von verschiedenen Diensten abgerufen und auf der Client-Seite zusammengeführt. Der Client ist normalerweise ein Browser, der die Daten über asynchrone Ajax-Aufrufe lädt.

Wie es funktioniert

Dieses Muster nutzt das asynchrone Laden von Daten, wie in Abbildung 4-12 dargestellt. Wenn ein Browser, der dieses Muster verwendet, zum Beispiel eine Webseite lädt, wird zuerst ein Teil der Webseite geladen und gerendert, während der Rest der Webseite geladen wird. Bei diesem Muster werden clientseitige Skripte wie JavaScript verwendet, um den Inhalt asynchron in den Webbrowser zu laden.

Client-Side Mashup at a web browser
Abbildung 4-12. Client-seitiges Mashup in einem Webbrowser

Anstatt den Nutzer längere Zeit warten zu lassen, indem alle Inhalte der Website auf einmal geladen werden, verwendet dieses Muster mehrere asynchrone Aufrufe, um verschiedene Teile der Website abzurufen, und rendert jedes Fragment, wenn es ankommt. Diese Anwendungen werden auch als Rich Internet Applications(RIAs) bezeichnet.

Wie es in der Praxis verwendet wird

Dieses Muster kann verwendet werden, wenn wir verfügbare Daten so schnell wie möglich präsentieren wollen, während wir später mehr Details liefern, oder wenn wir den Eindruck erwecken wollen, dass die Webseite viel schneller lädt.

Kritische Daten mit geringer Latenzzeit präsentieren

Nehmen wir den Anwendungsfall eines E-Commerce-Systems wie Amazon: Wenn der Nutzer eine Produktdetailseite aufruft, sollten wir in der Lage sein, alle wichtigen Daten, die der Nutzer erwartet, mit möglichst geringer Latenz zu präsentieren. Das Abrufen dieser Produktbewertungen und das Laden von Bildern kann einige Zeit in Anspruch nehmen. Deshalb rendern wir die Seite mit den grundlegenden Produktdetails mit dem Standardbild und verwenden dann Ajax-Aufrufe, um weitere Produktbilder und -bewertungen zu laden und die Webseite dynamisch zu aktualisieren. Mit diesem Ansatz können wir dem Nutzer die wichtigsten Daten viel schneller zur Verfügung stellen, als darauf zu warten, dass alle Daten abgerufen werden.

Den Eindruck erwecken, dass die Webseite schneller geladen wird

Wenn wir lose zusammenhängende HTML-Inhalte abrufen und die Webseite aufbauen, während der Nutzer sie lädt, und wenn wir es dem Nutzer ermöglichen, einen Teil der Inhalte zu sehen, während der Rest geladen wird, können wir den Eindruck erwecken, dass die Website schneller geladen wird. Das hält den Nutzer so lange bei der Stange, bis der Rest der Daten verfügbar ist, und kann letztlich das Nutzererlebnis verbessern.

Überlegungen

Verwende dieses Muster nur, wenn die zuerst geladenen Teildaten dem Nutzer präsentiert oder sinnvoll verwendet werden können. Wir raten davon ab, dieses Muster zu verwenden, wenn die abgerufenen Daten mit späteren Daten über eine Art von Join kombiniert und umgewandelt werden müssen, bevor sie dem Nutzer präsentiert werden können.

Zusammenfassung der Muster für die Datenzusammensetzung

In diesem Abschnitt wurden häufig verwendete Muster für die Datenzusammensetzung bei der Entwicklung von Cloud Native Applications beschrieben. Tabelle 4-2 fasst zusammen, wann wir diese Muster verwenden sollten und wann nicht, sowie die Vorteile der einzelnen.

Tabelle 4-2. Muster der Datenzusammensetzung
Muster Wann verwendet man Wann man nicht verwenden sollte Vorteile
Datendienst Die Daten gehören nicht einem einzelnen Microservice, aber mehrere Microservices sind für ihren Betrieb von den Daten abhängig. Die Daten können eindeutig mit einem bestehenden Microservice verknüpft werden, da die Einführung unnötiger Microservices auch zu einer komplexen Verwaltung führen kann. Reduziert die Kopplung zwischen den Diensten.
Bietet mehr Kontrolle/Sicherheit bei den Operationen, die mit den gemeinsamen Daten durchgeführt werden können.
Zusammengesetzte Datendienste Viele Kunden fragen mehrere Dienste ab, um ihre gewünschten Daten zu konsolidieren, und diese Konsolidierung ist allgemein genug, um von den Kunden wiederverwendet zu werden. Nur ein Kunde braucht die Konsolidierung.
Von Kunden durchgeführte Operationen können nicht verallgemeinert werden, um von vielen Kunden wiederverwendet zu werden.
Reduziert doppelte Arbeit, die von den Clients geleistet wird, und fasst sie in einem gemeinsamen Dienst zusammen.
Bietet mehr Ausfallsicherheit durch die Verwendung von Caches oder statischen Daten.
Client-seitiges Mashup Einige sinnvolle Operationen können mit partiellen Daten durchgeführt werden, z. B. das Rendern von nicht-abhängigen Daten in Webbrowsern. Die unabhängig abgerufenen Daten müssen verarbeitet werden, z. B. durch eine Verknüpfung, bevor die Antwort gesendet wird. Führt zu reaktionsschnelleren Anwendungen.
Reduziert die Wartezeit.

Muster für die Datenskalierung

Wenn die Last in nativen Cloud-Anwendungen steigt, kann entweder der Dienst oder der Speicher zum Engpass werden. Die Muster für die Skalierung von Diensten werden in Kapitel 3 erläutert. Hier werden wir sehen, wie wir Daten skalieren können. Wenn die Daten als Big Data eingestuft werden können, können wir NoSQL-Datenbanken oder verteilte Dateisysteme verwenden. Diese Systeme übernehmen die schwere Arbeit der Skalierung und Partitionierung der Daten und reduzieren die Komplexität der Entwicklung und Verwaltung.

Dennoch können die Konsistenz- und Transaktionsanforderungen geschäftskritischer Anwendungen den Einsatz relationaler Datenbanken erfordern. Da relationale Datenbanken nicht standardmäßig skalierbar sind, müssen wir möglicherweise die Anwendungsarchitektur ändern, um eine Skalierbarkeit der Daten zu erreichen. In diesem Abschnitt tauchen wir in die Muster ein, die uns helfen können, die Skalierung von Datenspeichern zu erleichtern, um Daten optimal zu speichern und abzurufen.

Daten-Splitting-Muster

Beim Data Sharding-Pattern wird der Datenspeicher in Shards unterteilt, die eine einfache Speicherung und Abfrage im großen Maßstab ermöglichen. Die Daten werden nach einem oder mehreren Attributen partitioniert, damit wir leicht erkennen können, in welchem Shard sie sich befinden.

Wie es funktioniert

Um die Daten zu splitten, können wir horizontale, vertikale oder funktionale Ansätze verwenden. Schauen wir uns diese drei Optionen im Detail an:

Horizontale Datenaufteilung

Jeder Shard hat dasselbe Schema, enthält aber anhand seines Sharding-Schlüssels unterschiedliche Datensätze. Eine Tabelle in einer Datenbank wird auf der Grundlage dieser Sharding-Schlüssel auf mehrere Knoten aufgeteilt. Zum Beispiel können Benutzerbestellungen gemeinsam genutzt werden, indem die Bestell-ID in drei Shards aufgeteilt wird, wie in Abbildung 4-13 dargestellt.

Horizontal data sharding using hashing
Abbildung 4-13. Horizontale Datenaufteilung mit Hashing
Vertikale Datenaufteilung

Jeder Shard muss kein identisches Schema haben und kann verschiedene Datenfelder enthalten. Jeder Shard kann eine Reihe von Tabellen enthalten, die sich nicht in einem anderen Shard befinden müssen. Das ist nützlich, wenn wir die Daten nach der Häufigkeit des Datenzugriffs aufteilen müssen; wir können die Daten, auf die am häufigsten zugegriffen wird, in einem Shard unterbringen und den Rest in einen anderen Shard verschieben. Abbildung 4-14 zeigt, wie häufig auf die Benutzerdaten zugegriffen wird und wie sie von den anderen Daten getrennt werden.

Vertical data sharding based on frequency of data access
Abbildung 4-14. Vertikale Datenaufteilung basierend auf der Häufigkeit des Datenzugriffs
Funktionales Sharding von Daten

Die Daten werden nach funktionalen Anwendungsfällen aufgeteilt. Anstatt alle Daten zusammen aufzubewahren, können die Daten auf der Grundlage unterschiedlicher Funktionen in verschiedene Shards aufgeteilt werden. Dies entspricht auch dem Prozess der Aufteilung von Funktionen in separate funktionale Dienste in der Cloud Native Application Architecture. Abbildung 4-15 zeigt, wie Produktdaten und Bewertungen in zwei Datenspeicher aufgeteilt werden.

Functional data sharding by segregating product details and reviews into two data stores
Abbildung 4-15. Funktionales Datensharing durch Aufteilung von Produktdetails und Bewertungen in zwei Datenspeicher

Cloud-native Anwendungen können alle drei Ansätze nutzen, um Daten zu skalieren, aber es gibt eine Grenze dafür, wie weit das vertikale und funktionale Sharding die Daten abtrennen kann. Letztendlich muss horizontales Data Sharding eingesetzt werden, um die Daten weiter zu skalieren. Wenn wir horizontales Data Sharding verwenden, können wir eine der folgenden Techniken einsetzen, um den Ort zu finden, an dem wir die Daten gespeichert haben:

Lookup-basiertes Data Sharding
Ein Nachschlagedienst oder ein verteilter Cache wird verwendet, um die Zuordnung zwischen dem Shard-Schlüssel und dem tatsächlichen Speicherort der physischen Daten zu speichern. Beim Abrufen der Daten überprüft die Client-Anwendung zunächst den Lookup-Service, um den tatsächlichen physischen Speicherort für den gewünschten Shard-Schlüssel zu ermitteln, und greift dann von dort auf die Daten zu. Wenn die Daten zu einem späteren Zeitpunkt neu aufgeteilt werden, muss der Client erneut den aktualisierten Speicherort der Daten abrufen.
Bereichsbezogene Datenaufteilung
Dieser spezielle Sharding-Ansatz kann angewendet werden, wenn der Sharding-Schlüssel fortlaufende Zeichen enthält. Die Daten werden in Bereichen geteilt, und wie beim Lookup-basierten Sharding kann ein Lookup-Dienst verwendet werden, um festzustellen, wo der jeweilige Datenbereich verfügbar ist. Dieser Ansatz liefert die besten Ergebnisse für Sharding-Schlüssel, die auf Datum und Uhrzeit basieren. Ein Datenbereich von einem Monat kann sich zum Beispiel im selben Shard befinden, so dass der Dienst alle Daten in einem Durchgang abrufen kann, anstatt mehrere Shards abzufragen.
Hash-basierte Datenaufteilung
Die Erstellung eines Shard-Schlüssels auf Basis der Datenfelder oder die Aufteilung der Daten nach Datumsbereichen führt nicht immer zu ausgewogenen Shards. Manchmal müssen wir die Daten zufällig verteilen, um ausgewogenere Shards zu erzeugen. Dies kann durch hashbasiertes Data Sharding erreicht werden, bei dem Hashes auf Basis des Shard-Schlüssels erstellt und zur Bestimmung des Speicherorts der Shard-Daten verwendet werden. Diese Methode ist nicht die beste, wenn Daten in Bereichen abgefragt werden, aber sie ist ideal, wenn einzelne Datensätze abgefragt werden. Hier können wir auch einen Nachschlagedienst verwenden, um den Hash-Schlüssel und die Zuordnung des Shard-Standorts zu speichern, um das Laden der Daten zu erleichtern.

Damit Sharding sinnvoll ist, sollten die Daten ein Feld oder eine Sammlung von Feldern enthalten, die die Daten eindeutig identifizieren oder sie sinnvoll in Teilmengen gruppieren. Die Kombination dieser Felder ergibt den Shard-/Partitionsschlüssel, der zum Auffinden der Daten verwendet wird. Die Werte in den Feldern, die zum Shard-Schlüssel beitragen, sollten fest sein und bei Datenaktualisierungen nicht geändert werden. Denn wenn sie sich ändern, ändern sie auch den Shard-Schlüssel, und wenn der aktualisierte Shard-Schlüssel nun auf einen anderen Shard-Speicherort verweist, müssen die Daten auch vom aktuellen Shard zum neuen Shard-Speicherort migriert werden. Das Verschieben von Daten zwischen Shards ist zeitaufwändig und sollte daher um jeden Preis vermieden werden.

Wie es in der Praxis verwendet wird

Dieses Muster kann verwendet werden, wenn die Daten nicht mehr in einem einzigen Knotenpunkt gespeichert werden können oder wenn die Daten verteilt werden müssen, um mit geringerer Latenz darauf zugreifen zu können.

Skalierung über einen einzelnen Knoten hinaus

Dieses Muster kann nützlich sein, wenn Ressourcen wie Speicherung, Rechenleistung oder Netzwerkbandbreite zu einem Engpass werden. Die Fähigkeit eines Systems, vertikal zu skalieren, ist immer dann begrenzt, wenn weitere Ressourcen wie Festplattenspeicher, Arbeitsspeicher oder Netzwerkbandbreite hinzugefügt werden; früher oder später werden der Anwendung die Ressourcen ausgehen. Anstatt an kurzfristigen Lösungen zu arbeiten, hilft dir die Partitionierung der Daten und die horizontale Skalierung dabei, über die Kapazität eines einzelnen Knotens hinaus zu skalieren.

Daten trennen, um die Zeit für die Datenabfrage zu verbessern

Wir können die Daten von trennen, indem wir mehrere Datenfelder kombinieren, um spezielle Splitterschlüssel zu erstellen. Stellen wir uns zum Beispiel vor, wir haben einen Online-Modeshop und haben einen Shard-Schlüssel erstellt, der ein Kleid type und brand kombiniert, um Daten zu speichern. Wenn wir den Typ und die Marke des Kleides kennen, nach dem wir suchen, können wir es dem entsprechenden Shard zuordnen und die Daten schnell abrufen. Wenn wir aber nur die type und size des Kleides kennen, können wir keinen gültigen Shard-Schlüssel konstruieren. In diesem Fall müssen wir alle Shards durchsuchen, um die Übereinstimmung zu finden, und das kann unsere Leistung stark beeinträchtigen.

Dieses Problem kann durch den Aufbau von hierarchischen Splitterschlüsseln gelöst werden. Wir können zum Beispiel den Schlüssel mit dem Kleid type / brand erstellen. Wenn wir die Art des Kleides kennen, können wir alle Shards, die dieses Kleid enthalten, nachschlagen type und sie dann nach dem bestimmten Kleid durchsuchen size. Dadurch wird die Anzahl der Scherben, die wir durchsuchen müssen, eingeschränkt und die Leistung verbessert. Wenn wir eine noch bessere Leistung für die Kombination type und size benötigen, können wir sekundäre Indizes erstellen, die sie verwenden. Diese sekundären Shard-Schlüssel können uns helfen, die Daten mit geringer Latenz abzurufen. Die Verwendung von sekundären Indizes kann jedoch die Kosten für die Datenänderung erhöhen, da wir nun auch die sekundären Shard-Schlüssel aktualisieren müssen, wenn Daten aktualisiert werden.

Wir können die Daten auch nach Datum und Zeitspanne unterteilen. Wenn wir zum Beispiel Bestellungen bearbeiten, sind wir wahrscheinlich mehr an den jüngsten Bestellungen interessiert als an den alten. Wir können die Daten nach Zeitspannen splitten und die jüngsten Bestellungen (z. B. die des letzten Monats oder des letzten Quartals) in einem Hot Shard und den Rest in einer Reihe von archivierten Shards speichern. So können wir die kritischen Daten effizient abrufen. In diesem Fall sollten wir die Daten auch regelmäßig vom Hot Shard in die Archiv-Shards verschieben, wenn sie veraltet sind.

Geografische Verteilung der Daten

Wenn die Kunden geografisch verteilt sind, können wir die Daten nach Regionen splitten und die relevanten Daten näher zu ihnen bringen. Bei einer Einzelhandelswebsite können zum Beispiel Details zu den Produkten, die in jeder Region verkauft werden, lokal gespeichert und bereitgestellt werden. So können mehr Anfragen in kürzerer Zeit bearbeitet werden.

Manche Kunden möchten vielleicht Produkte aus der ganzen Welt kaufen. Um eine Anfrage zu erfüllen, müssen wir also möglicherweise Daten von mehreren Shards abrufen, die über verschiedene Regionen verteilt sind. Damit dieser Anwendungsfall effizient funktioniert, müssen wir die Kunden so modellieren, dass sie eine Fan-out-Anfrage an alle Shards senden und gleichzeitig Daten abrufen können. Wenn ein Nutzer z. B. nach Produkten sucht, können wir eine Fan-out-Anfrage senden und nur mit den ersten 10 schnellsten Einträgen antworten, die wir erhalten. Wenn der Nutzer jedoch nach den günstigsten Optionen nach Namen sucht, müssen wir möglicherweise warten, bis die Antwort von allen Shards eintrifft, bevor wir die Ergebnisse anzeigen. Beachte, dass wir die Leistung möglicherweise mit Caching verbessern können. Darauf gehen wir später in diesem Kapitel ein.

Überlegungen

Wenn wir dieses Muster verwenden, ist es wichtig, die Shards so gut wie möglich auszugleichen, damit die Last gleichmäßig verteilt wird. Es ist auch notwendig, die Last in jedem Shard zu überwachen und einen Rebalance durchzuführen, wenn die Last nicht gleichmäßig verteilt ist. Ein Ungleichgewicht kann im Laufe der Zeit entstehen, wenn neue Daten durch Einfügungen, Löschungen oder ein verändertes Abfrageverhalten entstehen. Bedenke, dass bei Big Data ein Rebalancing der Datenspeicher einige Stunden bis Tage dauern kann.

Um das Rebalancing zu erleichtern, empfehlen wir, die Shards einigermaßen klein zu halten. In den ersten Tagen des Systems, wenn die Daten und die Last gering sind, können alle Shards auf demselben Knoten leben. Wenn die Last steigt, kann einer oder eine Gruppe von Shards auf andere Knoten verlagert werden. Dies ermöglicht nicht nur eine bessere Skalierbarkeit auf lange Sicht, sondern macht auch jede Shard-Migration relativ klein, sodass der Datenabgleich schneller und mit weniger Unterbrechungen für das gesamte System erfolgen kann.

Es ist auch wichtig, mehrere Kopien von Shards zu haben, um Ausfälle zu verkraften. Selbst wenn ein Knotenpunkt ausfällt, können wir auf dieselben Daten in einem anderen Knotenpunkt zugreifen, so dass wir Wartungsarbeiten durchführen können, ohne dass das gesamte System ausfällt.

Wenn es um die Verarbeitung von Datenaggregationen über Shards hinweg geht, verhalten sich verschiedene Aggregationen unterschiedlich. Aggregationen wie Summe, Durchschnitt, Minimum und Maximum können die Daten in jeder Partition isoliert verarbeiten, die Ergebnisse abrufen und sie kombinieren, um das Endergebnis zu ermitteln. Im Gegensatz dazu erfordern Aggregationsoperationen wie der Median die gesamten Daten auf einmal, so dass dies bei der Verwendung von Sharded-Daten nicht mit hoher Präzision umgesetzt werden kann.

Wir raten davon ab, bei der Erstellung von Shard-Schlüsseln automatisch inkrementierende Felder zu verwenden. Shards kommunizieren nicht miteinander, und aufgrund der Verwendung von automatisch inkrementierenden Feldern können mehrere Shards dieselben Schlüssel erzeugt haben und lokal auf unterschiedliche Daten mit diesen Schlüsseln verweisen. Das kann zu einem Problem werden, wenn die Daten während des Datenabgleichs neu verteilt werden.

Außerdem ist es wichtig, Shard-Schlüssel zu wählen, die zu ziemlich ausgewogenen Shards führen. Ohne ausgewogene Shards kann die erwartete Skalierbarkeit nicht erreicht werden. Der größte Shard wird immer derjenige mit der schlechtesten Leistung sein und irgendwann zu Engpässen führen.

Befehls- und Abfrageverantwortung Trennungsmuster

Das Command and Query Responsibility Segregation(CQRS) Muster trennt Aktualisierungen und Abfragen eines Datensatzes und ermöglicht es, sie auf verschiedenen Datenspeichern auszuführen. Dies führt zu einer schnelleren Aktualisierung und Abfrage von Daten. Außerdem erleichtert es die Modellierung von Daten für verschiedene Anwendungsfälle, erreicht eine hohe Skalierbarkeit und Sicherheit und ermöglicht es, dass sich Aktualisierungs- und Abfragemodelle unabhängig voneinander mit minimalen Interaktionen weiterentwickeln können.

Wie es funktioniert

Wir können Befehle (Aktualisierungen/Schreiben) und Abfragen (Lesen) trennen, indem wir verschiedene Dienste erstellen, die jeweils dafür zuständig sind(Abbildung 4-16). Dies erleichtert nicht nur die Ausführung von Diensten für Aktualisierungen und Lesevorgänge auf verschiedenen Knoten, sondern hilft auch dabei, für diese Vorgänge geeignete Dienste zu modellieren und die Dienste unabhängig voneinander zu skalieren.

Separating command and query operations
Abbildung 4-16. Trennung von Befehls- und Abfrageoperationen

Der Befehl und die Abfrage sollten keine datenbankspezifischen Informationen enthalten, sondern eher High-Level-Daten, die für die Anwendung relevant sind. Wenn ein Befehl an einen Dienst gesendet wird, extrahiert dieser die Informationen aus der Nachricht und aktualisiert den Datenspeicher. Dann sendet er diese Informationen als Ereignis asynchron an die Dienste, die die Abfragen bedienen, so dass diese ihr Datenmodell aufbauen können. Das Event-Sourcing-Muster mit einem logbasierten Warteschlangensystem wie Kafka kann verwendet werden, um die Ereignisse zwischen den Diensten weiterzuleiten. Auf diese Weise können die Abfragedienste Daten aus den Ereigniswarteschlangen lesen und Massenaktualisierungen in ihren lokalen Speichern vornehmen, und zwar im optimalen Format für die Bereitstellung dieser Daten.

Wie es in der Praxis verwendet wird

Wir können dieses Muster verwenden, wenn wir unterschiedliche Domänenmodelle für Befehle und Abfragen verwenden wollen und wenn wir Aktualisierungen und Datenabrufe aus Leistungs- und Sicherheitsgründen trennen müssen. Schauen wir uns diese Ansätze im Detail an.

Verschiedene Domänenmodelle für Befehl und Abfrage verwenden

Auf einer Einzelhandelswebsite können wir die Produktdetails und Bestandsinformationen in einer normalisierten relationalen Datenbank speichern. Das könnte die beste Wahl sein, um die Bestandsdaten bei jedem Kauf effizient zu aktualisieren. Für die Abfrage dieser Daten über einen Browser ist dies jedoch möglicherweise nicht die beste Option, da das Zusammenführen und Konvertieren der Daten in JSON zeitaufwändig sein kann. In diesem Fall können wir dieses Muster verwenden, um asynchron einen Abfragedatensatz zu erstellen, z. B. einen Dokumentenspeicher, der Daten im JSON-Format speichert, und diesen für die Abfrage verwenden. Dann haben wir getrennte optimierte Datenmodelle für Befehls- und Abfrageoperationen.

Da Befehls- und Abfragemodelle nicht eng aneinander gekoppelt sind, können wir verschiedene Teams mit befehls- und abfragebezogenen Anwendungen betrauen und beide Modelle je nach Anwendungsfall unabhängig voneinander weiterentwickeln.

Verteilen Sie Operationen und reduzieren Sie Datenkonflikte

Dieses Muster kann verwendet werden, wenn Cloud-native Anwendungen leistungsintensive Aktualisierungsvorgänge wie Daten- und Sicherheitsvalidierungen oder Nachrichtentransformationen oder leistungsintensive Abfragevorgänge mit komplexen Joins oder Datenzuordnungen haben. Wenn dieselbe Instanz des Datenspeichers sowohl für Befehle als auch für Abfragen verwendet wird, kann dies aufgrund der höheren Belastung des Datenspeichers zu einer schlechten Gesamtleistung führen. Durch die Aufteilung der Befehls- und Abfrageoperationen eliminiert CQRS nicht nur die Auswirkungen der einen auf die andere, indem es die Leistung und Skalierbarkeit des Systems verbessert, sondern hilft auch, Operationen zu isolieren, die eine höhere Sicherheitsdurchsetzung erfordern.

Da dieses Muster die Ausführung von Befehlen und Abfragen in verschiedenen Geschäften ermöglicht, können die Befehls- und Abfragesysteme auch unterschiedliche Skalierungsanforderungen haben. Im Anwendungsfall der Einzelhandelswebsite gibt es mehr Abfragen als Befehle und mehr Produktdetailansichten als tatsächliche Käufe. Daher können die meisten Dienste die Abfrageoperationen unterstützen und ein paar Dienste die Aktualisierungen durchführen.

Überlegungen

Da dieses Muster die Befehls- und Abfrageoperationen voneinander trennt, kann es eine hohe Verfügbarkeit gewährleisten. Selbst wenn einige Befehls- oder Abfragedienste nicht mehr verfügbar sind, wird das gesamte System nicht angehalten. Bei diesem Muster können wir die Abfrageoperationen unendlich skalieren, und mit einer angemessenen Anzahl von Replikationen können die Abfrageoperationen garantiert keine Ausfallzeiten verursachen. Bei der Skalierung von Befehlsoperationen müssen wir eventuell Muster wie Data Sharding verwenden, um Daten zu partitionieren und potenzielle Merge-Konflikte zu vermeiden.

CQRS wird nicht empfohlen, wenn eine hohe Konsistenz zwischen Befehls- und Abfrageoperationen erforderlich ist. Wenn Daten aktualisiert werden, werden die Aktualisierungen über Ereignisse asynchron an die Abfragespeicher gesendet, indem Muster wie Event Sourcing verwendet werden. Daher solltest du CQRS nur verwenden, wenn eine eventuelle Konsistenz tolerierbar ist. Das Erreichen einer hohen Konsistenz durch synchrone Datenreplikation wird in Cloud-nativen Anwendungsumgebungen nicht empfohlen, da dies zu Sperrkonflikten und hohen Latenzzeiten führen kann.

Wenn wir dieses Muster verwenden, können wir unter Umständen nicht automatisch getrennte Befehls- und Abfragemodelle mithilfe von Tools wie dem objektrelationalen Mapping (ORM) erstellen. Die meisten dieser Tools verwenden Datenbankschemata und erzeugen in der Regel kombinierte Modelle, so dass wir die Modelle möglicherweise manuell ändern oder von Grund auf neu schreiben müssen.

Hinweis

Auch wenn dieses Muster faszinierend aussieht, solltest du dich daran erinnern, dass es die Systemarchitektur sehr komplex machen kann. Wir müssen nun verschiedene Datenquellen durch das Versenden von Ereignissen über das Event Sourcing Pattern auf dem neuesten Stand halten und mit Ereignisduplikaten und Fehlern umgehen. Wenn die Befehls- und Abfragemodelle recht einfach sind und die Geschäftslogik nicht komplex ist, raten wir dir daher dringend davon ab, dieses Muster zu verwenden. Es kann mehr Komplexität in die Verwaltung bringen, als es Vorteile bringt.

Zusammenfassung der Datenskalierungsmuster

In diesem Abschnitt wurden häufig verwendete Muster für die Datenskalierung bei der Entwicklung nativer Cloud-Anwendungen vorgestellt. Tabelle 4-3 fasst zusammen, wann wir diese Muster verwenden sollten und wann nicht und welche Vorteile sie jeweils bieten .

Tabelle 4-3. Daten-Skalierungsmuster
Muster Wann verwendet man Wann man nicht verwenden sollte Vorteile
Data Sharding Daten enthalten ein Feld oder eine Sammlung von Feldern, die die Daten eindeutig identifizieren oder die Daten sinnvoll in Teilmengen gruppieren. Der Scherbenschlüssel kann keine gleichmäßig ausgeglichenen Scherben erzeugen.
Die in den Daten durchgeführten Operationen erfordern die Verarbeitung des gesamten Datensatzes, z. B. die Ermittlung eines Medians aus dem Datensatz.
Gruppiert Shards auf der Grundlage der bevorzugten Felder, die den Shard-Schlüssel ergeben.
Erstellt geografisch optimierte Shards, die näher an die Kunden gebracht werden können.
Baut hierarchische Shards oder zeitbereichsbasierte Shards, um die Suchzeit zu optimieren.
Verwendet sekundäre Indizes, um Daten mit Hilfe von Nicht-Shard-Schlüsseln abzufragen.
Command and Query Responsibility Segregation (CQRS)

Anwendungen haben leistungsintensive Aktualisierungsvorgänge mit:

  • Datenüberprüfungen

  • Sicherheitsprüfungen

  • Umwandlung von Nachrichten

Für leistungsintensive Abfrageoperationen wie komplexe Joins oder Datenmapping.

Eine hohe Konsistenz zwischen Befehl (Update) und Abfrage (Read) ist erforderlich.
Befehls- und Abfragemodelle sind näher beieinander.
Reduziert die Auswirkungen zwischen Befehls- und Abfrageoperationen.
Speichert Befehls- und Abfragedaten in zwei verschiedenen Datenspeichern, die den jeweiligen Anwendungsfällen entsprechen.
Setzt getrennte Sicherheitsrichtlinien für Befehle und Abfragen durch.
Ermöglicht es verschiedenen Teams, Anwendungen zu besitzen, die für Befehls- und Abfrageoperationen zuständig sind.
Bietet hohe Verfügbarkeit.

Muster für die Leistungsoptimierung

In verteilten cloud-nativen Anwendungen sind Daten oft die häufigste Ursache für Engpässe. Daten lassen sich nur schwer skalieren, da Konsistenzanforderungen zu Sperrkonflikten und Synchronisations-Overhead führen können. All dies führt zu Systemen, die schlecht funktionieren.

Eine primitive Möglichkeit, die Leistung zu verbessern, ist die Indizierung von Daten. Dies verbessert zwar die Suchleistung, aber eine übermäßige Verwendung von Indizes kann sowohl die Lese- als auch die Schreibleistung beeinträchtigen. Bei jedem Schreibvorgang müssen alle Indizes aktualisiert werden, was dazu führt, dass die Datenbanken mehrere Schreibvorgänge durchführen müssen. Auch bei Lesevorgängen sind die Datenspeicher möglicherweise nicht in der Lage, alle Indizes zu laden und im Speicher zu halten. Jede Abfrage muss möglicherweise mehrere Lesevorgänge durchführen, was die Zeit zum Abrufen der Daten verlängert.

Die Denormalisierung von Daten ist auch eine gute Technik zur Vereinfachung von Lesemodellen, da sie die Notwendigkeit von Joins beseitigen und die Leseleistung drastisch verbessern kann. Dies kann besonders nützlich sein, wenn wir diesen Ansatz mit dem CQRS-Muster kombinieren, da Autoren normalisierte Datenspeicher verwenden können, um eine hohe Konsistenz aufrechtzuerhalten, während Abfragen effizient von denormalisierten Daten lesen können.

Neben diesen einfachen Techniken gibt es noch weitere Möglichkeiten, die Leistung zu verbessern, indem man die Daten näher an die Ausführung heranbringt, die Ausführung näher an die Daten heranbringt, die Menge der übertragenen Daten reduziert oder vorverarbeitete Daten für die spätere Verwendung speichert. In diesem Abschnitt werden solche Muster im Detail besprochen.

Materialisiertes Ansichtsmuster

Das Materialized View-Pattern bietet die Möglichkeit, Daten bei Abfragen effizient abzurufen, indem es die Daten näher an die Ausführung verlagert und materialisierte Ansichten vorfüllt. Dieses Muster speichert alle relevanten Daten eines Dienstes in seinem lokalen Datenspeicher und formatiert die Daten optimal, um die Abfragen zu bedienen, anstatt den Dienst bei Bedarf abhängige Dienste für Daten aufrufen zu lassen.

Wie es funktioniert

Dieses Muster repliziert und verschiebt Daten aus abhängigen Diensten in seinen lokalen Datenspeicher und erstellt materialisierte Ansichten(Abbildung 4-17). Außerdem werden optimale Sichten erstellt, um die Daten effizient abzufragen, ähnlich wie bei dem Muster Composite Data Services .

Service built with the Materialized View pattern
Abbildung 4-17. Mit dem Materialized View-Pattern erstellter Dienst

Bei diesem Muster werden die Daten der abhängigen Dienste asynchron repliziert. Wenn Datenbanken die asynchrone Datenreplikation unterstützen, können wir sie nutzen, um Daten von einem Datenspeicher zum anderen zu übertragen. Fehlschlägt dies, müssen wir das Event Sourcing Muster verwenden und die Daten mithilfe von Ereignisströmen replizieren. Der Quelldienst sendet jede Einfüge-, Lösch- und Aktualisierungsoperation asynchron an einen Event Stream, der die Daten an die Dienste weiterleitet, die die materialisierten Ansichten erstellen, wo sie die Daten abrufen und in ihre lokalen Speicher laden. In Kapitel 5 wird das Event Sourcing-Pattern im Detail erläutert.

Wie es in der Praxis verwendet wird

Wir können dieses Muster verwenden, wenn wir die Effizienz der Datenabfrage durch den Wegfall komplexer Joins verbessern und die Kopplung mit abhängigen Diensten reduzieren wollen.

Verbessere die Effizienz der Datenabfrage

Dieses Muster wird verwendet, wenn ein Teil der Daten lokal verfügbar ist und der Rest aus externen Quellen mit hoher Latenzzeit abgerufen werden muss. Wenn wir z. B. eine Produktdetailseite einer E-Commerce-Anwendung bereitstellen, die Kommentare und Bewertungen von einem relativ langsamen Bewertungsdienst abruft, könnten wir die Daten mit hoher Latenz für den Nutzer darstellen. Mit diesem Muster können die Gesamtbewertung und die vorberechneten besten und schlechtesten Kommentare in den Datenspeicher des Produktdetaildienstes repliziert werden, um die Effizienz des Datenabrufs zu verbessern.

Selbst wenn wir Daten in dieselbe Datenbank bringen, kann es manchmal kostspielig sein, mehrere Tabellen zu verbinden. In diesem Fall können wir Techniken wie relationale Datenbankansichten verwenden, um Daten in einer leicht abfragbaren materialisierten Ansicht zusammenzufassen. Wenn wir dann Produktdetails abrufen müssen, kann der Detaildienst die Daten mit hoher Effizienz bereitstellen.

Zugang zu nicht sensiblen Daten, die in sicheren Systemen gehostet werden, gewähren

In einigen Anwendungsfällen von kann der aufrufende Dienst auf nicht sensible Daten angewiesen sein, die sich hinter einer Sicherheitsschicht befinden, so dass der Dienst sich authentifizieren und Validierungsprüfungen durchlaufen muss, bevor er die Daten abrufen kann. Mit diesem Muster können wir die nicht sensiblen Daten, die für den Dienst relevant sind, replizieren und dem aufrufenden Dienst den direkten Zugriff auf die Daten in seinem lokalen Speicher ermöglichen. Dieser Ansatz macht nicht nur unnötige Sicherheitsprüfungen und Validierungen überflüssig, sondern verbessert auch die Leistung.

Überlegungen

Manchmal werden die abhängigen Daten in verschiedenen Arten von Datenspeichern gespeichert oder diese Speicher enthalten viele unnötige Daten. In diesem Fall sollten wir nur die relevante Teilmenge der Daten replizieren und sie in einem Format speichern, mit dem die materialisierte Ansicht erstellt werden kann. Dies verbessert die Gesamtabfrageleistung, da die Daten lokal verwendet werden, und verringert die Bandbreitennutzung bei der Übertragung der Daten. Wir sollten immer die asynchrone Datenreplikation verwenden, da die synchrone Datenreplikation zu Sperrkonflikten und hohen Latenzzeiten führen kann.

Das Materialized View-Pattern verbessert nicht nur die Leistung des Dienstes, indem es die Zeit für den Datenabruf verkürzt, sondern vereinfacht auch die Dienstlogik, indem es unnötige Datenverarbeitung und die Notwendigkeit, abhängige Dienste zu kennen, eliminiert.

Dieses Muster bietet auch Ausfallsicherheit. Da die Daten in den lokalen Speicher repliziert werden, kann der Dienst seine Operationen ohne Unterbrechung durchführen, selbst wenn der Quelldienst, der die Daten bereitgestellt hat, nicht verfügbar ist.

Wir raten davon ab, dieses Muster zu verwenden, wenn Daten von abhängigen Diensten mit geringer Latenz abgerufen werden können, wenn sich die Daten in den abhängigen Diensten schnell ändern oder wenn die Konsistenz der Daten als wichtig für die Antwort angesehen wird. In diesen Fällen kann dieses Muster zu unnötigem Overhead und inkonsistentem Verhalten führen.

Dieses Muster ist nicht ideal, wenn die Datenmenge, die verschoben werden muss, sehr groß ist oder die Daten häufig aktualisiert werden. Dies kann zu Verzögerungen bei der Replikation und einer hohen Netzwerkbandbreite führen, was die Genauigkeit und Leistung der Anwendung beeinträchtigt. Für diese Anwendungsfälle solltest du das Data Locality Pattern (siehe nächste Seite) verwenden.

Muster der Datenlokalität

Das Ziel des Data Locality Pattern ist es, die Ausführung näher an die Daten zu bringen. Dies geschieht, indem die Dienste mit den Daten zusammengelegt werden oder indem die Ausführung im Datenspeicher selbst erfolgt. Dadurch kann die Ausführung mit weniger Einschränkungen auf die Daten zugreifen, was zu einer schnelleren Ausführung beiträgt und die Bandbreite durch das Senden aggregierter Ergebnisse reduziert.

Wie es funktioniert

Die Verlagerung der Ausführung kann die Leistung stärker verbessern als die Verlagerung der Daten. Wenn genügend CPU-Ressourcen zur Verfügung stehen, kann das Hinzufügen eines Dienstes für die Abfrage am Datenknoten, wie in Abbildung 4-18 gezeigt, die Leistung verbessern, indem der Großteil der Daten lokal verarbeitet wird, anstatt sie über das Netzwerk zu übertragen.

Moving a microservice closer to the data store
Abbildung 4-18. Einen Microservice näher an den Datenspeicher rücken

Wenn der Dienst nicht an denselben Knotenpunkt verlegt werden kann, kann die Verlegung des Dienstes in dieselbe Region oder dasselbe Rechenzentrum helfen, die Bandbreite besser zu nutzen. Dieser Ansatz kann auch dazu beitragen, dass der Dienst Ergebnisse zwischenspeichert und daraus effizienter bedient.

Wir können die Ausführung auch näher an die Daten heranbringen, indem wir sie als gespeicherte Prozeduren in den Datenspeicher verlagern(Abbildung 4-19). Dies ist eine gute Möglichkeit, die Fähigkeiten relationaler Datenbanken zu nutzen, um die Datenverarbeitung und -abfrage zu optimieren.

Moving execution to data stores as stored procedures
Abbildung 4-19. Verlagerung der Ausführung in Datenspeicher als Stored Procedures

Wie es in der Praxis verwendet wird

Dieses Muster fördert die Kopplung der Ausführung mit den Daten, um die Latenzzeit zu verringern und Bandbreite zu sparen, so dass verteilte Cloud-native Anwendungen effizient über das Netzwerk arbeiten können.

Verringerung der Latenzzeit beim Abrufen von Daten

Wir können dieses Muster verwenden, wenn wir Daten aus einer oder mehreren Datenquellen abrufen und eine Art von Verknüpfung durchführen müssen. Um Daten zu verarbeiten, muss ein Dienst alle Daten in seinen lokalen Speicher holen, bevor er eine sinnvolle Operation durchführen kann. Dazu müssen die Daten über das Netzwerk übertragen werden, was zu Latenzzeiten führt. Wenn du den Dienst näher an den Datenspeicher verlegst (oder, wenn mehrere Speicher beteiligt sind, in den Speicher, der den meisten Input liefert), werden weniger Daten über das Netzwerk übertragen, was die Zeit für die Datenabfrage verkürzt. Wir können dieses Muster auch für Kompositionsdienste verwenden, die Joins durchführen, indem sie Daten aus Datenspeichern und anderen Diensten abrufen. Indem wir diese Dienste näher an die Datenquelle rücken, können wir ihre Gesamtleistung verbessern.

Reduziere die Bandbreitennutzung beim Abrufen von Daten

Dieses Muster ist besonders nützlich, wenn wir Daten aus mehreren Quellen abrufen müssen, um Daten zu aggregieren oder zu filtern. Die Ausgabe dieser Abfragen wird deutlich kleiner sein als die Eingabe. Wenn die Ausführung näher an der Datenquelle erfolgt, muss nur eine kleine Datenmenge übertragen werden, was die Bandbreitennutzung verbessern kann. Das ist besonders nützlich, wenn die Datenspeicher riesig sind und die Kunden geografisch verteilt sind. Dies ist ein guter Ansatz, wenn Cloud-native Anwendungen mit Bandbreitenengpässen zu kämpfen haben.

Überlegungen

Die Anwendung des Datenlokalitätsmusters kann auch dazu beitragen, ungenutzte CPU-Ressourcen an den Datenknoten zu nutzen. Die meisten Datenknoten sind E/A-intensiv, und wenn die Abfragen, die sie ausführen, einfach genug sind, haben sie möglicherweise eine Menge CPU-Ressourcen im Leerlauf. Die Verlagerung der Ausführung auf den Datenknoten kann die Ressourcen besser ausnutzen und die Gesamtleistung optimieren. Wir sollten darauf achten, nicht alle Ausführungen auf die Datenknoten zu verlagern, da dies zu einer Überlastung der Knoten und zu Problemen beim Abrufen der Daten führen kann.

Dieses Muster ist nicht ideal, wenn Abfragen den größten Teil ihrer Eingaben ausgeben. In diesen Fällen werden die Datenknoten überlastet, ohne dass es zu Einsparungen bei der Bandbreite oder Leistung kommt. Die Entscheidung, wann dieses Muster verwendet werden sollte, hängt vom Kompromiss zwischen Bandbreite und CPU-Auslastung ab. Wir empfehlen, dieses Muster zu verwenden, wenn die durch die Verringerung des Datentransfers erzielten Gewinne viel größer sind als die zusätzlichen Ausführungskosten, die bei den Datenknoten entstehen.

Hinweis

Übertrage die Ausführung nur dann auf den Datenspeicher, wenn dieser Datenspeicher ausschließlich vom abfragenden Microservice verwendet wird. Die Ausführung von Stored Procedures in einer gemeinsam genutzten Datenbank ist nicht empfehlenswert, da dies Auswirkungen auf die Leistung und die Verwaltung haben kann. Außerdem ist die Änderungsverwaltung von Datenbanken nicht trivial, und wenn sie nicht sorgfältig durchgeführt wird, kann die Aktualisierung der Stored Procedure zu Ausfallzeiten führen. Verschiebe die Ausführung nur mit Vorsicht in den Datenspeicher und ziehe es vor, die Geschäftslogik im Microservice zu haben, wenn es keine wesentliche Leistungsverbesserung gibt.

Caching-Muster

Das Caching-Muster speichert bereits verarbeitete oder abgerufene Daten im Speicher und stellt diese Daten für ähnliche Abfragen in der Zukunft bereit. Dadurch wird nicht nur die wiederholte Datenverarbeitung in den Diensten reduziert, sondern auch der Aufruf von abhängigen Diensten vermieden, wenn die Antwort bereits im Dienst gespeichert ist.

Wie es funktioniert

Ein Cache ist normalerweise ein In-Memory-Datenspeicher, in dem zuvor verarbeitete oder abgerufene Daten gespeichert werden, damit wir diese Daten bei Bedarf wiederverwenden können, ohne sie erneut verarbeiten oder abrufen zu müssen. Wenn eine Anfrage gestellt wird, um Daten abzurufen, und wir die benötigten Daten im Cache finden können, haben wir einen Cache-Treffer. Wenn die Daten nicht im Cache vorhanden sind, liegt ein Cache-Miss vor.

Wenn ein Cache-Fehler auftritt, muss das System in der Regel Daten aus dem Datenspeicher verarbeiten oder abrufen und den Cache mit den abgerufenen Daten für künftige Referenzen aktualisieren. Dieser Vorgang wird als Read-Through Cache-Operation bezeichnet. Wenn eine Anfrage zur Aktualisierung der Daten gestellt wird, sollten wir diese im Datenspeicher aktualisieren und alle relevanten, zuvor abgerufenen Einträge im Cache entfernen oder ungültig machen. Dieser Vorgang wird als "write-through cache operation" bezeichnet. In diesem Fall ist die Invalidierung wichtig, denn wenn die Daten erneut angefordert werden, sollte der Cache nicht die alten Daten zurückgeben, sondern die aktualisierten Daten mit Hilfe der Read-Through-Cache-Operation aus dem Speicher abrufen. Dieses Lese- und Aktualisierungsverhalten wird gemeinhin als "Cache aside" bezeichnet, und die meisten kommerziellen Caches unterstützen diese Funktion standardmäßig.

Das Caching von Daten kann entweder auf der Client- oder der Serverseite oder auf beiden Seiten erfolgen, und der Cache selbst kann lokal (Speicherung von Daten in einer Instanz) oder gemeinsam (Speicherung von Daten in einer verteilten Weise) sein.

Vor allem, wenn der Cache nicht gemeinsam genutzt wird, kann er nicht ständig neue Daten hinzufügen, da der verfügbare Speicher irgendwann aufgebraucht ist. Daher werden einige Datensätze entfernt, um Platz für neue Daten zu schaffen. Die beliebteste Räumungsrichtlinie ist dieLRU-Richtlinie (LRU), bei der Daten, die über einen längeren Zeitraum nicht genutzt wurden, entfernt werden, um Platz für neue Einträge zu schaffen. Andere Richtlinien sind First in, first out(FIFO), bei der der älteste geladene Eintrag entfernt wird, Most Recently Used(MRU), bei der der zuletzt verwendete Eintrag entfernt wird, und Trigger-basierte Optionen, die Einträge auf der Grundlage von Werten im Trigger-Event entfernen. Wir sollten die für unseren Anwendungsfall geeignete Räumungsrichtlinie verwenden.

Wenn Daten im Cache gespeichert werden, können die im Datenspeicher gespeicherten Daten von anderen Anwendungen aktualisiert werden, so dass es zu Inkonsistenzen zwischen den Daten im Cache und im Speicher kommen kann, wenn die Daten lange im Cache gehalten werden. Dies wird durch die Verwendung einer Ablaufzeit für jeden Cache-Eintrag verhindert. Dadurch werden die Daten bei einer Zeitüberschreitung neu aus dem Datenspeicher geladen und die Konsistenz zwischen Cache und Datenspeicher verbessert.

Wie es in der Praxis verwendet wird

Dieses Muster wird in der Regel angewandt, wenn dieselbe Abfrage von einem oder mehreren Clients mehrfach aufgerufen werden kann, vor allem wenn wir nicht genug Wissen darüber haben, welche Daten als nächstes abgefragt werden.

Verbessere die Zeit zum Abrufen von Daten

Caching kann verwendet werden, wenn der Abruf von Daten aus dem Datenspeicher viel mehr Zeit erfordert als der Abruf aus dem Cache. Das ist besonders nützlich, wenn der ursprüngliche Speicher komplexe Operationen durchführen muss oder an einem entfernten Standort eingesetzt wird und die Netzwerklatenz daher hoch ist.

Verbessern Sie das Laden statischer Inhalte

Das Caching eignet sich am besten für statische Daten oder für Daten, die selten aktualisiert werden. Besonders wenn die Daten statisch sind und im Speicher abgelegt werden können, können wir den gesamten Datensatz in den Cache laden und den Cache so konfigurieren, dass er nicht abläuft. Dadurch wird die Zeit für das Abrufen der Daten drastisch verkürzt und das Laden der Daten aus der ursprünglichen Datenquelle überflüssig.

Verringern von Konflikten im Datenspeicher

Da es die Anzahl der Aufrufe des Datenspeichers reduziert, können wir dieses Muster verwenden, um die Konkurrenzsituation im Datenspeicher zu verringern oder wenn der Speicher durch viele gleichzeitige Anfragen überlastet ist. Wenn die Anwendung, die die Daten konsumiert, Inkonsistenzen tolerieren kann, z. B. wenn die Daten ein paar Minuten veraltet sind, können wir dieses Muster auch bei schreibintensiven Datenspeichern einsetzen, um die Leselast zu verringern und die Stabilität des Systems zu verbessern. In diesem Fall werden die Daten im Cache schließlich konsistent, wenn die Cache-Zeit abgelaufen ist.

Vorabruf von Daten, um die Zeit für den Datenabruf zu verbessern

Wir können den Cache ganz oder teilweise vorladen, wenn wir die Art der Abfragen kennen, die wahrscheinlich gestellt werden. Wenn wir zum Beispiel Bestellungen bearbeiten und wissen, dass die Anwendungen hauptsächlich die Daten der letzten Woche abrufen, können wir den Cache mit den Daten der letzten Woche vorladen, wenn wir den Dienst starten. Das kann eine bessere Leistung bringen als das Laden von Daten bei Bedarf. Wenn das Vorladen weggelassen wird, können der Dienst und der Datenspeicher stark belastet werden, da die meisten anfänglichen Anfragen zu einem Cache-Miss führen.

Dieses Muster kann auch verwendet werden, wenn wir wissen, welche Daten als nächstes abgefragt werden. Wenn ein Nutzer zum Beispiel auf einer Einzelhandelswebsite nach Produkten sucht und wir nur die ersten 10 Einträge anzeigen, wird er wahrscheinlich die nächsten 10 Einträge anfordern. Das Vorladen der nächsten 10 Einträge in den Cache kann Zeit sparen, wenn diese Daten benötigt werden.

Hohe Verfügbarkeit durch Lockerung der Datenspeicherabhängigkeit erreichen

Das Caching kann auch genutzt werden, um eine hohe Verfügbarkeit zu erreichen, insbesondere wenn die Verfügbarkeit des Dienstes wichtiger ist als die Konsistenz der Daten. Wir können Dienstaufrufe mit zwischengespeicherten Daten bearbeiten, auch wenn der Backend-Datenspeicher nicht verfügbar ist. Wie in Abbildung 4-20 dargestellt, können wir dieses Muster auch erweitern, indem wir den lokalen Cache auf einen gemeinsamen oder verteilten Cache zurückgreifen lassen, der wiederum auf den Datenspeicher zurückgreifen kann, wenn die Daten nicht vorhanden sind. Dieses Muster kann das in Kapitel 3 besprochene Resilient-Connectivity-Muster mit einem Circuit-Breaker für die Fallback-Aufrufe einbeziehen, so dass diese erneut versuchen können, eine Verbindung herzustellen, wenn die Backends nach einem Ausfall wieder verfügbar sind.

Multilayer cache fallback
Abbildung 4-20. Multilayer Cache Fallback

Wenn wir einen gemeinsam genutzten Cache verwenden, können wir auch eine zweite Cache-Instanz als Standby einrichten und die Daten dorthin replizieren, um die Verfügbarkeit zu verbessern. So können unsere Anwendungen auf den Standby-Cache zurückgreifen, wenn der primäre Cache fehlschlägt.

Mehr Daten zwischenspeichern, als ein einzelner Knoten aufnehmen kann

Verteilte Caching-Systeme können als weitere Alternative eingesetzt werden, wenn der lokale Cache oder der gemeinsame Cache nicht alle benötigten Daten enthalten kann. Sie bieten außerdem Skalierbarkeit und Ausfallsicherheit durch Partitionierung und Replikation von Daten. Diese Systeme unterstützen Durchlese- und Durchschreiboperationen und können die Datenspeicher direkt aufrufen, um Daten abzurufen und zu aktualisieren. Außerdem können wir sie skalieren, indem wir bei Bedarf einfach weitere Cache-Server hinzufügen.

Obwohl verteilte Caches viele Daten speichern können, sind sie nicht so schnell wie der lokale Cache und machen das System komplexer. Möglicherweise sind zusätzliche Netzwerksprünge nötig, um Daten abzurufen, und wir müssen nun eine zusätzliche Anzahl von Knoten verwalten. Am wichtigsten ist, dass sich alle am verteilten Cache beteiligten Knoten im selben Netzwerk befinden und über eine relativ hohe Bandbreite verfügen, da es sonst zu Verzögerungen bei der Datensynchronisierung kommen kann. Wenn die Kunden hingegen geografisch verteilt sind, kann ein verteilter Cache die Daten näher an die Kunden bringen, was zu schnelleren Antwortzeiten führt.

Überlegungen

Der Cache sollte nie als einzige Quelle der Wahrheit verwendet werden und muss nicht mit Blick auf eine hohe Verfügbarkeit entwickelt werden. Auch wenn die Caches nicht verfügbar sind, sollte die Anwendung in der Lage sein, die erwarteten Funktionalitäten auszuführen. Da Caches Daten im Arbeitsspeicher speichern, besteht immer die Möglichkeit eines Datenverlusts, daher sollten Datenspeicher verwendet werden, um Daten für eine langfristige Nutzung zu erhalten.

In manchen Fällen sind die meisten Daten in einer Antwortnachricht statisch, und nur ein kleiner Teil der Daten wird häufig aktualisiert. Wenn die Erstellung des statischen Teils der Daten teuer ist, kann es von Vorteil sein, die Datensätze in zwei Teile aufzuteilen, in statische und dynamische Teile, und dann nur die statischen Teile im Cache zu speichern. Beim Aufbau der Antwort können wir die im Cache gespeicherten statischen Daten und die dynamisch erzeugten Daten kombinieren.

Als Alternative zu den Cache-Räumungsrichtlinien können wir auch lokale Caches einrichten, um Datenüberläufe zu unterstützen. Diese Überlaufdaten werden auf die Festplatte geschrieben. Du solltest diesen Ansatz nur verwenden, wenn das Lesen der Daten von der Festplatte viel schneller ist als das Abrufen von Daten aus dem ursprünglichen Datenspeicher. Dieser Ansatz kann zusätzliche Komplexität mit sich bringen, da wir nun auch den Cache-Überlauf verwalten müssen.

Hinweis

Das Cache-Timeout sollte optimal eingestellt sein, also weder zu lang noch zu kurz. Ein zu langes Cache-Timeout kann zwar zu größeren Inkonsistenzen führen, aber ein zu kurzes Timeout ist auch nachteilig, da die Daten dann zu oft neu geladen werden und der Zweck des Cachens von Daten verfehlt wird. Eine lange Zeitüberschreitung kann aber auch von Vorteil sein, wenn die Kosten für das Abrufen der Daten deutlich höher sind als die Kosten für inkonsistente Daten.

Der größte Nachteil der lokalen Zwischenspeicherung von Daten ist, dass bei der Skalierung der Dienste jeder Dienst seinen eigenen lokalen Cache hat und die Daten zu unterschiedlichen Zeiten mit den Datenspeichern synchronisiert. Es kann sein, dass der eine Dienst eine Aktualisierung vor dem anderen erhält, was dazu führen kann, dass die Caches der verschiedenen Microservices nicht mehr synchron sind. Wenn dann dieselbe Anfrage an zwei Dienste gesendet wird, könnten diese mit unterschiedlichen Werten antworten, weil die Cache-Invalidierung nur in dem Dienst stattfindet, der die ursprüngliche Anfrage bearbeitet, und die Caches in den anderen Microservices von dieser Invalidierung nichts mitbekommen. Diese Situation kann auch eintreten, wenn die Daten auf der Ebene des Datenspeichers repliziert werden, da die Caches in den Microservices nichts von diesen Aktualisierungen wissen.

Wie in Abbildung 4-21 dargestellt, können wir dieses Problem entschärfen, indem wir bei Datenaktualisierungen alle Caches ungültig machen, indem wir die Cache-Knoten entweder über ein Nachrichtensystem über die Aktualisierung informieren, wie im Publisher-Subscriber Muster, oder indem wir das Event Sourcing Muster verwenden. Beide Muster werden in Kapitel 5 behandelt.

Using the message broker to invalidate cache in all services
Abbildung 4-21. Verwendung des Message Brokers, um den Cache in allen Diensten zu invalidieren
Hinweis

Die Einführung unnötiger Cache-Schichten kann einen hohen Speicherverbrauch verursachen, die Leistung verringern und zu Dateninkonsistenzen führen. Wir empfehlen dringend, bei der Einführung einer Cache-Lösung einen Lasttest durchzuführen und insbesondere den Prozentsatz der Cache-Treffer sowie die Leistung, CPU- und Speichernutzung zu überwachen. Ein niedriger Prozentsatz an Cache-Treffern kann darauf hinweisen, dass der Cache nicht effektiv ist. In diesem Fall solltest du entweder den Cache ändern, um einen höheren Prozentsatz an Cache-Treffern zu erzielen, oder andere Alternativen wählen. Die Vergrößerung des Caches, die Reduzierung des Cache-Ablaufs und das Vorladen des Caches sind Optionen, die wir nutzen können, um die Cache-Treffer zu verbessern.

Wann immer es möglich ist, empfehlen wir, die Daten in den Caches im Batch-Verfahren zu aktualisieren, so wie es auch in Datenspeichern gemacht wird. Das optimiert die Bandbreite und verbessert die Leistung bei hoher Auslastung. Wenn mehrere Cache-Einträge gleichzeitig aktualisiert werden, können die Aktualisierungen entweder nach einem optimistischen oder einem pessimistischen Ansatz erfolgen. Beim optimistischen Ansatz gehen wir davon aus, dass keine gleichzeitigen Aktualisierungen stattfinden und überprüfen den Cache nur auf gleichzeitige Schreibvorgänge, bevor wir den Cache aktualisieren. Beim pessimistischen Ansatz hingegen sperren wir den Cache für den gesamten Aktualisierungszeitraum, sodass keine gleichzeitigen Aktualisierungen stattfinden können. Dieser Ansatz ist nicht skalierbar und sollte daher nur für sehr kurzlebige Vorgänge verwendet werden.

Wir empfehlen außerdem, den Cache zwangsweise ablaufen zu lassen oder neu zu laden. Wenn der Kunde zum Beispiel auf andere Weise von einer möglichen Aktualisierung weiß, kann er den Cache zwangsweise neu laden, bevor er Daten abruft. Dies können wir erreichen, indem wir beim Speichern der Daten eine Zufallsvariable als Teil des Cache-Schlüssels einführen. Der Kunde kann denselben Schlüssel immer wieder verwenden und ihn nur dann ändern, wenn er ein Neuladen erzwingen muss. Dieser Ansatz wird z. B. von Web-Clients gegen das Browser-Caching eingesetzt. Da Browser Daten gegen eine Anfrage-URI zwischenspeichern, können die Clients durch ein zufälliges Element in der URI den zwischengespeicherten Eintrag zwangsweise neu laden, indem sie dieses zufällige URI-Element beim Senden der Anfrage einfach ändern. Sei vorsichtig, wenn du diese Technik mit Kunden von Drittanbietern verwendest, denn sie können die Zufallsvariable ständig ändern und das System überlasten. Wenn die Clients jedoch unter der Kontrolle desselben Teams stehen, kann dies ein praktikabler Ansatz sein.

Einige kommerzielle Cache-Dienste können Datensicherheit bieten, indem sie das Vault Key Pattern verwenden, das später in diesem Kapitel behandelt wird. Die meisten Caches sind jedoch nicht für die Sicherheit ausgelegt und sollten nicht direkt mit externen Systemen verbunden werden. Um Sicherheit zu erreichen, können wir einen Datendienst über den Cache legen, indem wir das Muster Datendienst verwenden und API-Sicherheit für den Datendienst anwenden(Abbildung 4-22). Dadurch werden die Daten geschützt und nur autorisierte Dienste können Daten im Cache lesen und schreiben.

Securely exposing the cache to external services
Abbildung 4-22. Den Cache sicher für externe Dienste zugänglich machen

Hosting-Muster für statische Inhalte

Das Static Content Hosting-Pattern stellt statische Inhalte in Datenspeichern bereit, die sich näher am Kunden befinden, damit die Inhalte mit geringer Latenz und ohne übermäßigen Verbrauch von Rechenressourcen direkt an den Kunden geliefert werden können.

Wie es funktioniert

Cloud-native Webservices werden verwendet, um dynamische Inhalte auf der Grundlage von Kundenanfragen zu erstellen. Einige Kunden, vor allem Browser, benötigen viele andere statische Inhalte, wie statische HTML-Seiten, JavaScript- und CSS-Dateien, Bilder und Dateien zum Herunterladen. Anstatt Microservices für statische Inhalte zu verwenden, können wir mit diesem Muster statische Inhalte direkt von Speicherdiensten wie Content Delivery Networks (CDNs) bereitstellen.

Abbildung 4-23 veranschaulicht dieses Muster im Zusammenhang mit einer Webanwendung. Wenn der Browser Daten anfordert, können wir den Dienst dazu bringen, mit dynamischem HTML zu antworten, das eingebettete Links zu relevanten statischen Daten enthält, die an verschiedenen Stellen der Seite gerendert werden müssen. So kann der Browser die statischen Inhalte anfordern, die dann über DNS aufgelöst und vom nächstgelegenen CDN bereitgestellt werden.

Browser loading dynamic content from a microservice while loading static content from a CDN
Abbildung 4-23. Browser lädt dynamische Inhalte von einem Microservice, während statische Inhalte aus einem CDN geladen werden

Wie es in der Praxis verwendet wird

Dieses Muster wird verwendet, wenn wir statische Inhalte schnell und mit geringer Antwortzeit an Kunden liefern müssen und wenn wir die Belastung der Rendering-Dienste reduzieren müssen.

Schnellere Bereitstellung statischer Inhalte

Da sich statische Inhalte nicht ändern, werden die Daten in verschiedenen Umgebungen und geografischen Standorten repliziert und zwischengespeichert, um sie näher an die Kunden zu bringen. Dies kann dazu beitragen, statische Daten mit geringer Latenz zu liefern.

Reduzierung der Ressourcenauslastung bei Rendering-Diensten

Wenn wir sowohl statische als auch dynamische Daten an die Kunden senden müssen, wie im vorangegangenen Webbrowser-Beispiel beschrieben, können wir die statischen Daten abtrennen und in ein Speichersystem wie ein CDN oder einen S3-Bucket verschieben, damit die Kunden diese Daten direkt abrufen können. Dadurch wird die Ressourcenauslastung des Microservices, der die dynamischen Inhalte wiedergibt, reduziert, da er nicht alle statischen Inhalte in seine Antwort packen muss.

Überlegungen

Wir können dieses Muster nicht verwenden, wenn die statischen Inhalte vor der Auslieferung an die Kunden aktualisiert werden müssen, z. B. durch Hinzufügen der aktuellen Zugriffszeit und des Standorts zur Webantwort. Außerdem ist dies keine praktikable Lösung, wenn die Menge der zu übermittelnden statischen Daten gering ist. Die Kosten für den Client, die Daten aus mehreren Quellen anzufordern, können zu einer größeren Latenz führen, als wenn sie direkt vom Dienst übermittelt werden. Wenn du sowohl statische als auch dynamische Inhalte senden musst, empfehlen wir, dieses Muster nur dann zu verwenden, wenn es einen erheblichen Leistungsvorteil bietet.

Wenn du dieses Muster verwendest, solltest du dich daran erinnern, dass du möglicherweise komplexere Client-Implementierungen benötigst. Der Grund dafür ist, dass der Client in der Lage sein sollte, auf der Grundlage der eingehenden dynamischen Daten die entsprechenden statischen Inhalte abzurufen und beide Datentypen auf der Client-Seite zu kombinieren. Wenn wir dieses Muster für andere Anwendungsfälle als das Rendern von Webseiten in einem Browser verwenden, müssen wir auch in der Lage sein, komplexe Clients zu erstellen und auszuführen, um diesen Anwendungsfall zu erfüllen.

Manchmal müssen wir statische Daten sicher speichern. Wenn wir autorisierten Nutzern den Zugriff auf statische Daten über dieses Muster erlauben müssen, können wir das Datendienst-Muster zusammen mit der API-Sicherheit oder dem Vault-Schlüssel-Muster verwenden, um den Datenspeicher zu sichern.

Zusammenfassung der Muster für die Leistungsoptimierung

In diesem Abschnitt wurden häufig verwendete Muster zur Leistungsoptimierung bei der Entwicklung nativer Cloud-Anwendungen vorgestellt. Tabelle 4-4 fasst zusammen, wann wir diese Muster verwenden sollten und wann nicht, sowie die Vorteile der einzelnen.

Tabelle 4-4. Muster zur Leistungsoptimierung
Muster Wann verwendet man Wann man nicht verwenden sollte Vorteile
Materialisierte Ansicht Ein Teil der Daten ist lokal verfügbar, der Rest der Daten muss aus externen Quellen mit hoher Latenzzeit geholt werden.
Die Daten, die verschoben werden müssen, sind klein und werden selten aktualisiert.
Ermöglicht den Zugriff auf nicht sensible Daten, die in sicheren Systemen gehostet werden.
Daten können von abhängigen Diensten mit geringer Latenz abgerufen werden.
Die Daten in den abhängigen Diensten ändern sich schnell.
Die Konsistenz der Daten wird als wichtig für die Antwort angesehen.
Kann die Daten in jeder Datenbank speichern, die für die Anwendung geeignet ist.
Erhöht die Ausfallsicherheit des Dienstes durch Replikation der Daten in lokale Speicher.
Daten Lokalität Daten aus mehreren Datenquellen lesen und eine Verknüpfung oder Datenaggregation im Speicher durchführen.
Die Datenspeicher sind riesig und die Clients sind geografisch verteilt.
Abfragen geben den größten Teil ihrer Eingaben aus.
Die zusätzlichen Ausführungskosten, die an den Datenknoten entstehen, sind höher als die Kosten für die Datenübertragung über das Netzwerk.
Verringert die Auslastung der Netzwerkbandbreite und die Latenzzeit bei der Datenabfrage.
Nutzt die CPU-Ressourcen besser und optimiert die Gesamtleistung.
Zwischenspeichern von Ergebnissen und effizientere Bearbeitung von Anfragen.
Caching Am besten für statische Daten oder Daten, die häufiger gelesen als aktualisiert werden.
Die Anwendung hat dieselbe Abfrage, die wiederholt von einem oder mehreren Clients aufgerufen werden kann, vor allem wenn wir nicht wissen, welche Daten als nächstes abgefragt werden.
Der Datenspeicher unterliegt einem hohen Maß an Konflikten oder kann die Anzahl der gleichzeitigen Anfragen von mehreren Clients nicht bewältigen.
Die Daten werden häufig aktualisiert.
Als Mittel zur Speicherung des Zustands, da sie nicht als einzige Quelle der Wahrheit betrachtet werden sollten.
Die Daten sind kritisch, und das System kann keine Dateninkonsistenzen tolerieren.
Du kannst wählen, welcher Teil der Daten zwischengespeichert werden soll, um die Leistung zu verbessern.
Die Verwendung eines Caches an der Seite verbessert die Leistung, indem redundante Berechnungen reduziert werden.
Statische Daten können im Voraus in den Cache geladen werden.
In Kombination mit einer Räumungsrichtlinie kann der Cache die neuesten/erforderlichen Daten speichern.
Hosting statischer Inhalte Alle oder einige der vom Kunden angeforderten Daten sind statisch.
Die statischen Daten müssen in verschiedenen Umgebungen oder geografischen Standorten verfügbar sein.
Die statischen Inhalte müssen vor der Auslieferung an die Kunden aktualisiert werden, z. B. durch Hinzufügen der Zugriffszeit und des Standorts.
Die Menge an Daten, die bereitgestellt werden muss, ist gering.
Die Kunden können statische und dynamische Inhalte nicht gemeinsam abrufen und kombinieren.
Geografische Partitionierung und Speicherung näher am Kunden sorgen für kürzere Antwortzeiten und schnellere Zugriffs- und Downloadgeschwindigkeiten.
Reduziert die Ressourcenauslastung bei Rendering-Diensten.

Verlässlichkeitsmuster

Daten Verluste werden von keiner geschäftskritischen Anwendung toleriert, daher ist die Zuverlässigkeit der Daten von größter Bedeutung. Daher ist es wichtig, bei der Änderung von Datenspeichern und bei der Übertragung von Daten zwischen Anwendungen entsprechende Zuverlässigkeitsmechanismen anzuwenden. In diesem Abschnitt wird die Verwendung des Transaction Reliability Pattern beschrieben, um eine zuverlässige Speicherung und Verarbeitung von Daten zu gewährleisten.

Transaktionsmuster

Das Transaktionsmuster verwendet Transaktionen, um eine Reihe von Operationen als eine einzige Arbeitseinheit durchzuführen, sodass alle Operationen als Einheit abgeschlossen oder rückgängig gemacht werden. Dadurch wird die Integrität der Daten gewahrt und die Ausführung von Diensten wird fehlerfrei. Dies ist entscheidend für die erfolgreiche Ausführung von Finanzanwendungen.

Wie es funktioniert

Dieses Muster fasst mehrere einzelne Vorgänge zu einem einzigen großen Vorgang zusammen und bietet die Garantie, dass entweder alle Vorgänge oder kein Vorgang erfolgreich sein werden. Alle Vorgänge folgen diesen Schritten:

  1. Das System leitet eine Transaktion ein.

  2. Es werden verschiedene Datenmanipulationsoperationen durchgeführt.

  3. Commit wird verwendet, um das Ende der Transaktion anzuzeigen.

  4. Wenn es keine Fehler gibt, ist die Übergabe erfolgreich, die Transaktion wird erfolgreich abgeschlossen und die Änderungen werden in den Datenspeichern übernommen.

    Wenn Fehler auftreten, werden alle Vorgänge in der Transaktion zurückgesetzt und die Transaktion schlägt fehl. In den Datenspeichern werden keine Änderungen vorgenommen.

Wenn wir z. B. Benutzerbestellungen als Transaktion bearbeiten müssen, können wir eine Transaktion initiieren, die bestellte Produktmenge aus der Tabelle "Inventory" entfernen, sie der Tabelle "User Order" hinzufügen und schließlich eine Transaktionsübergabe durchführen. In diesem Fall werden beide Datenaktualisierungsvorgänge als eine einzige atomare Operation ausgeführt. Wenn die Bestandstabelle leer ist, schlägt die Transaktion fehl und das System kehrt in seinen Ausgangszustand zurück. Wenn jedoch beide Operationen erfolgreich sind, wird die Transaktion erfolgreich durchgeführt und die Änderungen werden im Datenspeicher gespeichert.

Das Transaktionsmuster hält sich an die folgenden ACID Eigenschaften:

Atomic
Alle Vorgänge müssen auf einmal stattfinden, oder es darf keiner stattfinden.
Konsistent
Vor und nach der Transaktion befindet sich das System in einem gültigen Zustand.
Isolierung
Die Ergebnisse, die durch gleichzeitige Transaktionen erzielt werden, sind identisch mit denen, die in sequenzieller Reihenfolge ausgeführt werden.
Langlebig
Wenn die Transaktion abgeschlossen ist, bleiben die übertragenen Änderungen auch bei Systemausfällen erhalten.

Wir können die Transaktionsisolierung auf verschiedenen Ebenen erreichen. Die höchste Stufe bietet die serialisierbare Isolierung. Sie sperrt den Datenzugriff auf ausgewählte Daten für parallele Lese- und Schreibabfragen während der Transaktion und blockiert das Hinzufügen und Entfernen von Daten, die in den Datenbereich der Transaktion fallen könnten. Wenn wir zum Beispiel mit einer Transaktion alle Benutzer unter 30 Jahren ändern, können wir nicht gleichzeitig einen neuen Benutzer mit 23 Jahren hinzufügen. Wiederholte Lesevorgänge Isolation bietet die zweitbeste Isolationsebene. Sie blockiert den Datenzugriff auf ausgewählte Daten für Lese- und Schreibabfragen während der Transaktion, erlaubt aber das Hinzufügen und Entfernen neuer Daten im Datenbereich der Transaktion. Gleichzeitig blockiert die Read-Committed-Isolierung nur das Schreiben von Daten, während die Read-Uncommitted-Isolierung das Lesen von nicht-kommittierten Aktualisierungen anderer Transaktionen ermöglicht.

Transaktionen werden in der Regel nur mit einem einzigen Datenspeicher, z. B. einer relationalen Datenbank, verwendet, aber wir können auch Vorgänge über mehrere Systeme hinweg koordinieren, z. B. Datenbanken, Ereignisströme und Warteschlangensysteme. Wenn zum Beispiel eine Bestellung aufgegeben wird, kann das System nicht nur die Datenbank aktualisieren, sondern auch einen Eintrag in die Warteschlange für Liefernachrichten hinzufügen, um das Fulfillment-Team über die Lieferung zu informieren - eine einzige Transaktion.

Solche Transaktionen zwischen mehreren Systemen werden von Konsensalgorithmen wie XA-Transaktionen, Paxos und Raft abgewickelt. Diese können zwei- und dreiphasige Commit-Protokolle verwenden, um sicherzustellen, dass die Vorgänge systemübergreifend koordiniert werden.

Wie es in der Praxis verwendet wird

Transaktionen können verwendet werden, um mehrere Vorgänge zu einer einzigen Arbeitseinheit zusammenzufassen und um die Vorgänge mehrerer Systeme zu koordinieren.

Kombiniere mehrere Vorgänge zu einer einzigen Arbeitseinheit

Wir können dieses Muster verwenden, um mehrere Schritte zu kombinieren, die alle vollständig abgearbeitet werden sollten, damit der Vorgang als gültig gilt. Eine Überweisung von 25 US-Dollar von Bob auf das Konto von Alice umfasst zum Beispiel zwei Schritte: 25 US-Dollar werden von Bobs Konto abgezogen und 25 US-Dollar auf das Konto von Alice überwiesen. Wenn einer dieser Schritte fehlschlägt, gilt der gesamte Vorgang als ungültig, und das System sollte alle vorgenommenen Änderungen rückgängig machen und die Konten in denselben Zustand versetzen wie vor Beginn der Transaktion.

Wir können auch dafür sorgen, dass sich mehrere Transaktionen nicht gegenseitig behindern. Zum Beispiel können sowohl Bob als auch Eva gleichzeitig Geld auf das Konto von Alice überweisen.

Kombiniere Vorgänge über mehrere Systeme hinweg

Dieses Muster kann verwendet werden, wenn wir ein Ereignis aus einer Warteschlange abrufen, eine darauf basierende Aktualisierung eines Datenspeichers durchführen und diese Nachricht zur weiteren Verarbeitung an eine andere Warteschlange weiterleiten wollen - alles in einer einzigen Transaktion, wie in Abbildung 4-24 dargestellt. Um die Vorgänge zwischen mehreren Systemen zu synchronisieren, können wir eine XA Transaktion verwenden, die ein zweiphasiges Commit-Protokoll verwendet. Die meisten Datenbanken und Warteschlangensysteme unterstützen XA-Transaktionen. So können wir sicherstellen, dass das Ereignis auch dann nicht verloren geht, wenn das Verarbeitungssystem mitten in der Ausführung fehlschlägt.

Simple message processing use case applying XA-Transaction
Abbildung 4-24. Einfacher Anwendungsfall der Nachrichtenverarbeitung unter Anwendung einer XA-Transaktion

Überlegungen

Wir brauchen dieses Muster nicht zu verwenden, wenn der Vorgang nur einen einzigen Schritt umfasst oder wenn es mehrere Schritte gibt, aber das Scheitern einiger Schritte als akzeptabel angesehen wird.

Es ist wichtig zu beachten, dass die Verwendung von Konsensalgorithmen wie XA-Transaktionen die Vorgänge synchronisiert und zu Latenzzeiten führt. Wir empfehlen, dieses Muster nur dann zu verwenden, wenn die Transaktion relativ kurzlebig ist und nur wenige Systeme involviert sind, wie wir es im Beispiel der Auftragsbearbeitung erläutert haben.

Wann immer es möglich ist, solltest du die Operation idempotent machen. Das macht Transaktionen überflüssig und vereinfacht das System. Denn bei idempotenten Aktualisierungen sind die Ergebnisse gleich, auch wenn derselbe Vorgang mehrmals durchgeführt wird. Nehmen wir zum Beispiel an, wir überschreiben immer einen Wert, wie die Anzahl der verfügbaren Artikel im Inventar. Auch wenn wir den Wert mehrmals überschreiben, hat das keine Auswirkungen auf das Endergebnis. Auf diese Weise können wir dasselbe Ereignis mehrmals senden, um Systemausfälle zu überwinden.

Wenn wir die Ausführung synchronisieren müssen und mehr als drei Systeme haben, empfehlen wir, das in Kapitel 3 besprochene Saga Muster zu verwenden. Dieses Muster ist nützlich, um Transaktionen zwischen mehreren Datenspeichern, Microservices und Message Brokern zu koordinieren. Es ermöglicht uns, mehrere Transaktionen nacheinander auszuführen und frühere Transaktionen zu kompensieren, wenn eine Transaktion fehlschlägt. Dadurch kann auch die hohe Latenz oder Kopplung reduziert werden, die durch die von XA-Transaktionen verwendeten verteilten Sperren entstehen kann. Wir können Saga aber nur nutzen, wenn alle beteiligten Transaktionen im Falle eines Fehlers durch eine Ausgleichstransaktion rückgängig gemacht werden können. Das kann vor allem dann zum Problem werden, wenn wir Systeme von Drittanbietern integrieren und möglicherweise keine Möglichkeit haben, diese im Falle eines Ausfalls zu kompensieren.

Wir empfehlen die Verwendung von XA-Transaktionen gegenüber Saga, wenn alle Aktualisierungen in einem einzigen Datenspeicher durchgeführt werden müssen oder wenn alle Schritte gleichzeitig als atomare Operation ausgeführt werden müssen. Während Saga die Transaktionen der Reihe nach ausführt, können andere Systeme parallel auf Daten aus den Datenspeichern und Microservices zugreifen. Sie können dann inkonsistente Ergebnisse erhalten, wenn sie einen Teil der Daten aus einem Datenspeicher abrufen, der die Transaktion bereits ausgeführt hat, und einen anderen aus einem Datenspeicher, der die Transaktion noch nicht verarbeitet hat.

Zusammenfassung des Musters der Transaktionszuverlässigkeit

In diesem Abschnitt wurde ein häufig verwendetes Muster vorgestellt, das bei der Entwicklung nativer Cloud-Anwendungen für Zuverlässigkeit sorgt. Tabelle 4-5 fasst zusammen, wann wir dieses Muster verwenden sollten und wann nicht, sowie seine Vorteile .

Tabelle 4-5. Muster der Transaktionszuverlässigkeit
Muster Wann verwendet man Wann man nicht verwenden sollte Vorteile
Transaktion Ein Vorgang umfasst mehrere Schritte und alle Schritte sollten automatisch verarbeitet werden, damit der Vorgang als gültig betrachtet wird. Die Anwendung hat nur einen einzigen Schritt in der Operation.
Die Anwendung hat mehrere Schritte, und der Ausfall einiger Schritte wird als akzeptabel angesehen.
Hält die ACID-Eigenschaften ein.
Verarbeitet mehrere unabhängige Transaktionen.

Sicherheit: Tresorschlüssel-Muster

Die Sicherung von Daten wird im Abschnitt "Sicherheit" ausführlich behandelt . Hier zeigen wir, wie der Zugriff auf Datenspeicher über das Vault Key-Pattern kontrolliert werden kann, um die Sicherheit bei der Entwicklung von Cloud Native Applications zu erhöhen.

Das Vault Key-Pattern ermöglicht den direkten Zugang zu Datenspeichern über ein vertrauenswürdiges Token, auch Vault Key genannt. Einige der beliebtesten Cloud-Datenspeicher unterstützen diese Funktion.

Wie es funktioniert

Das Vault Key Muster basiert auf einem vertrauenswürdigen Token, das vom Kunden vorgelegt und vom Datenspeicher validiert wird. Bei diesem Muster bestimmt die Anwendung, wer auf welchen Teil der Daten zugreifen darf.

Abbildung 4-25 veranschaulicht dieses Muster. Der Client oder der aufrufende Dienst ruft die Anwendung auf, um ein Token für den Zugriff auf den jeweiligen Datenspeicher zu erhalten. Die Anwendung kann ein Identitätsprovider sein oder einen Identitätsprovider kontaktieren, um den Aufrufer zu validieren und ein vertrauenswürdiges Tresorschlüssel-Token für den Zugriff auf den jeweiligen Datenspeicher auszustellen. Die Anwendung kann auch den Umfang der Operationen angeben, die der Aufrufer auf dem Datenspeicher durchführen kann. Außerdem wird der Schlüssel mit einer Verfallszeit versehen, so dass der Zugang zum Dienst nur für einen bestimmten Zeitraum möglich ist. Der Aufrufer kann dann die Ressource mit dem angegebenen Schlüssel aufrufen und autorisierte Vorgänge durchführen, bis der Schlüssel abläuft.

Actions performed by clients to retrieve data in the Vault Key pattern
Abbildung 4-25. Von Clients durchgeführte Aktionen zum Abrufen von Daten im Vault Key-Muster

Dieses Muster kann auch die Erneuerung der Tresorschlüssel nach Ablauf der Gültigkeit unterstützen, indem ein Refresh-Token verwendet wird, wie bei der API-Sicherheit. Das erleichtert den reibungslosen Betrieb von Diensten, ohne dass es zu Unterbrechungen bei der Neuautorisierung kommt.

Wie es in der Praxis verwendet wird

Dieses Muster kann verwendet werden, wenn der Datenspeicher den Identitätsanbieter nicht erreichen kann, um den Kunden beim Datenzugriff zu authentifizieren und zu autorisieren. Bei diesem Muster enthält der Datenspeicher das Zertifikat des Identitätsanbieters, so dass er in der Lage ist, das Token zu entschlüsseln und seine Echtheit zu überprüfen, ohne den Identitätsanbieter aufrufen zu müssen. Da er für die Validierung keine Remote-Service-Aufrufe tätigen muss, kann er auch Authentifizierungsvorgänge mit minimaler Latenzzeit durchführen.

Überlegungen

Sobald der aufrufende Dienst Zugriff auf den Datenspeicher erhält, verliert die Anwendung, die den Dienst steuert, normalerweise die Kontrolle. Dieses Muster bietet einen Mechanismus, um die Kontrolle über den Datenspeicher zurückzuhalten und die Sicherheit zu gewährleisten. Wir können dieses Muster jedoch nur anwenden, wenn der Datenspeicher eine Schlüsselvalidierung unterstützt. Dies ist wichtig, um sicherzustellen, dass das Token vom Identitätsanbieter ausgegeben wurde und nicht abgelaufen ist. Einige fortschrittliche Datenspeicher unterstützen auch Zugriffsbereiche; sie können festlegen, auf welchen Teil des Datenspeichers, z. B. eine Tabelle oder eine Zeile, die eingehende Anfrage zugreifen kann. Wenn der Datenspeicher den Zugriff nicht anhand von Schlüsseln validieren kann, solltest du alternative Ansätze verwenden, wie z. B. das Fronting der Speicher mit einem Datendienst und den Schutz durch API-Sicherheit.

Manchmal kann der ausgegebene Tresorschlüssel kompromittiert werden. In diesen Fällen ist es in der Regel nicht möglich, die Verwendung des Tokens zu sperren, da die meisten Datenspeicher diese Funktion nicht unterstützen. Wir können den Schaden, der durch einen kompromittierten Tresorschlüssel entstehen kann, verringern, indem wir die Verfallszeit auf einen moderaten Wert setzen.

Zusammenfassung des Tresorschlüsselmusters

In diesem Abschnitt wurde das häufig verwendete Vault Key-Pattern vorgestellt, das bei der Entwicklung nativer Cloud-Anwendungen für Sicherheit sorgt. Tabelle 4-6 fasst zusammen, wann wir dieses Muster verwenden sollten und wann nicht, sowie seine Vorteile .

Tabelle 4-6. Tresorschlüssel-Sicherheitsmuster
Muster Wann verwendet man Wann man nicht verwenden sollte Vorteile
Tresorschlüssel Sicherer Zugriff auf entfernte Daten mit minimaler Latenzzeit.
Der Store verfügt über eine begrenzte Rechenkapazität, um Serviceaufrufe zur Authentifizierung und Autorisierung durchzuführen.
Sie brauchen einen feinkörnigen Schutz der Daten.
Es muss genau festgelegt werden, welche Abfragen auf dem Datenspeicher ausgeführt werden sollen.
Der offene Datenspeicher kann den Zugriff nicht anhand von Schlüsseln überprüfen.
Direkter Zugriff auf Datenspeicher durch Verwendung eines vertrauenswürdigen Tokens, eines Tresorschlüssels
Minimale Betriebskosten im Vergleich zum Aufruf des zentralen Identitätsdienstes zur Validierung

Technologien zur Umsetzung von Datenmanagementmustern

Als Softwareentwickler oder Architekt von musst du die am besten geeigneten Technologien für deinen Anwendungsfall auswählen. Dazu gehört auch die Auswahl von Datenspeichern. Bei der Auswahl eines Datenspeichers musst du Faktoren wie die zu speichernden Daten, die Datenmenge (Skalierbarkeit), die erwartete Schreib- und Leseleistung, die Systemverfügbarkeit und die Konsistenz berücksichtigen. In diesem Abschnitt gehen wir auf die Arten von Datenspeichern ein, die am häufigsten für Cloud Native Applications verwendet werden, und wann du sie einsetzen kannst.

Relationale Datenbankmanagementsysteme

Die meisten traditionellen Datenbanken fallen unter die Kategorie der relationalen Datenbankmanagementsysteme (RDBMS), zu denen MySQL, Oracle, MSSQL, Postgres, H2 und andere gehören. Diese relationalen Datenbanken bieten die ACID-Eigenschaften und können mit ihrem SQL auch sehr komplexe Datenzugriffsmuster haben. Wenn du jedoch nicht-relationale Daten wie XML, JSON oder binäre Formate hast, ist ein RDBMS möglicherweise nicht die beste Option. Dann musst du ein verteiltes Dateisystem oder eine NoSQL-Datenbank für die Datenspeicherung wählen, wie bereits unter "Relationale Datenbanken" beschrieben .

Wenn du Cloud-native Anwendungen entwickelst, empfehlen wir dir, die Datenbank nicht selbst in der Cloud-Infrastruktur einzurichten, sondern eine verwaltete Version von RDBMS zu verwenden, die von einem Cloud-Anbieter bereitgestellt wird, z. B. Amazon Relational Database Service (RDS), Google Cloud SQL oder Azure SQL. Dies verringert nicht nur die Komplexität der Verwaltung der Datenbanken, sondern ist auch besser auf die Umgebung abgestimmt.

Um RDBMS zu skalieren, können wir sie als Primär- und Replikat-Datenbanken einsetzen, wie im Materialized View-Pattern beschrieben, oder die Daten sharen, wie im Sharding-Pattern. Im schlimmsten Fall, wenn wir immer noch Probleme mit dem Speicherplatz haben, können wir auch regelmäßig ältere, selten genutzte Daten in einem Archiv wie NoSQL sichern und sie aus dem Speicher löschen.

Apache Cassandra

Apache Cassandra ist eine verteilte NoSQL-Datenbank, die intern bei Facebook entstand und im Juli 2008 als Open-Source-Projekt veröffentlicht wurde. Der Spaltenspeicher Cassandra ist bekannt für seine kontinuierliche Verfügbarkeit (keine Ausfallzeiten), hohe Leistung und lineare Skalierbarkeit, die moderne Anwendungen und Microservices benötigen. Außerdem bietet er eine Replikation über Rechenzentren und geografische Grenzen hinweg, um die Verfügbarkeit in verschiedenen Regionen zu gewährleisten. Cassandra kann Petabytes an Daten und Tausende von gleichzeitigen Operationen pro Sekunde verarbeiten und ermöglicht es dir, große Datenmengen in hybriden Cloud- und Multicloud-Umgebungen zu verwalten. Für die Bereitstellung nativer Cloud-Anwendungen empfehlen wir verwaltete Cassandra-Bereitstellungen wie Amazon Keyspaces und Asta on Google Cloud.

Die Schreibleistung von Cassandra ist im Vergleich zur Leseleistung sehr hoch. Wie bereits in "NoSQL-Datenbanken" beschrieben , bietet Cassandra von vornherein eine mögliche Konsistenz. Wir können aber auch die Konsistenzstufen ändern, um je nach Anwendungsfall schwache oder starke Konsistenz zu erreichen.

Die Leistung von Cassandra hängt auch davon ab, wie wir Daten speichern und abfragen. Wenn wir Daten anhand einer Reihe von Schlüsseln abfragen, sollten wir den Zeilenschlüssel (Partitionsschlüssel) verwenden. Wenn wir Daten aus verschiedenen Schlüsseln abfragen müssen, können wir sekundäre Indizes erstellen. Übertreibe es nicht mit den sekundären Indizes; sie können den Datenspeicher verlangsamen, da bei jeder Einfügung auch die Indizes aktualisiert werden müssen. Außerdem ist Cassandra nicht effizient, wenn wir zwei Spaltenfamilien verbinden wollen, und wir sollten es nicht verwenden, wenn wir die Daten häufiger aktualisieren müssen .

Apache HBase

Apache HBase ist ein verteilter, skalierbarer NoSQL-Spalten-Speicher, der auf dem HDFS läuft. HBase ist in der Lage, sehr große Tabellen mit Milliarden von Zeilen und Millionen von Spalten zu hosten und kann außerdem in Echtzeit zufälligen Lese- und Schreibzugriff auf Hadoop-Daten bieten. Er skaliert linear über sehr große Datenmengen und kombiniert problemlos Datenquellen mit unterschiedlichen Strukturen und Schemata.

Da HBase ein Spaltenspeicher ist, unterstützt es dynamische Datenbankschemata, und da es auf dem HDFS läuft, kann es auch in MapReduce-Aufträgen verwendet werden. Daher ist das komplexe, voneinander abhängige System von HBase schwieriger zu konfigurieren, zu sichern und zu warten.

Im Gegensatz zu Cassandra verwendet HBase eine "Master/Worker"-Bereitstellung und kann daher einen Single Point of Failure aufweisen. Wenn deine Anwendung Hochverfügbarkeit erfordert, solltest du Cassandra HBase vorziehen. Wenn es jedoch stark auf die Konsistenz der Daten ankommt, ist HBase besser geeignet, da es die Daten nur an einen Ort schreibt und immer weiß, wo sie zu finden sind (da die Datenreplikation "extern" von HDFS durchgeführt wird). Ähnlich wie Cassandra ist auch HBase nicht gut für häufige Datenlöschungen oder -aktualisierungen geeignet.

MongoDB

MongoDB ist ein Dokumentenspeicher, der die Speicherung von Daten in JSON-ähnlichen Dokumenten unterstützt, wie in "NoSQL-Datenbanken" beschrieben . Dokumente und Sammlungen in MongoDB sind vergleichbar mit Datensätzen und Tabellen in relationalen Datenbanken. MongoDB verwendet die Abfragesprache, um auf die gespeicherten Daten zuzugreifen, die Aggregation, Filterung und Sortierung auf der Grundlage beliebiger Dokumentenfelder durchzuführen und Felder einzufügen oder zu löschen, ohne die Dokumente umzustrukturieren. MongoDB Cloud bietet MongoDB als gehostete Lösung für die Nutzung nativer Cloud-Anwendungen an.

Im Gegensatz zu Cassandra oder RDBMSs bevorzugt MongoDB mehr Indizes. Wenn sie nicht indiziert ist, kann die Leistung leiden, da sie die gesamte Sammlung durchsuchen muss. MongoDB bevorzugt außerdem die Konsistenz gegenüber der Verfügbarkeit. Die Verfügbarkeit wird durch ein einziges primäres Lese-/Schreibsystem und mehrere sekundäre Replikate erreicht. Wenn ein primäres Replikat nicht mehr verfügbar ist, werden die Lese- und Schreibvorgänge vorübergehend für 10 bis 40 Sekunden unterbrochen, während MongoDB automatisch eines seiner sekundären Replikate als primäres Replikat wählt.

MongoDB wird häufig für mobile Anwendungen, Content Management, Echtzeit-Analysen und IoT-Anwendungen eingesetzt. MongoDB ist auch eine gute Wahl, wenn du keine klare Schemadefinition für deine JSON-Dokumente hast und du eine gewisse Nichtverfügbarkeit des Datenspeichers tolerieren kannst. Wie andere NoSQL-Datenbanken ist sie jedoch nicht für transaktionale Daten geeignet.

Redis

Redis ist ein In-Memory-Schlüsselwert-Datenspeicher, der häufig als Cache verwendet wird, wie unter im Abschnitt "Caching Pattern" beschrieben . Er unterstützt String-Schlüssel und verschiedene Arten von Werten wie Strings, Listen, Sets, sortierte Sets, Hashes, Bit-Arrays und vieles mehr. Das macht die Anwendung weniger komplex, da sie ihre interne Datenstruktur nun direkt in Redis speichern kann. Redis ist ideal für einen Cache, da es Transaktionen, Schlüssel mit einer begrenzten Lebensdauer, LRU-Eviction von Schlüsseln, automatische Ausfallsicherung und die Möglichkeit, überschüssige Daten auf die Festplatte zu schreiben, unterstützt. Redis bietet außerdem zahlreiche Cloud-Hosting-Optionen für Cloud-native Anwendungen an, darunter AWS, Google, Redis Labs und IBM.

Redis unterstützt zwei Arten von Persistenzoptionen: Redis Database Backup (RDB) und Append Only File (AOF). Mit beiden Optionen können wir eine gute Schreibleistung und ein hohes Maß an Datensicherheit bei Systemausfällen erreichen. Redis bietet Hochverfügbarkeit durch die Verwendung eines einzigen "Masters" und mehrerer "Replikate" wie im CQRS-Muster und Skalierbarkeit durch das Sharding von "Master" und "Replikaten", wie im "Data Sharding Pattern" beschrieben .

Redis ist jedoch kein NoSQL-Ersatz für relationale Datenspeicher, da es viele Standardfunktionen von relationalen Datenspeichern nicht unterstützt, wie z. B. effiziente Abfragen und die Durchführung komplexer Datenmanipulationen und Aggregationsoperationen.

Amazon DynamoDB

DynamoDB ist eine Key-Value- und Dokumentendatenbank, die zum Speichern und Abrufen von Daten mit geringer Latenz und hoher Skalierbarkeit verwendet werden kann. Sie kann mehr als 10 Billionen Anfragen pro Tag und in Spitzenzeiten mehr als 20 Millionen Anfragen pro Sekunde verarbeiten. Die Daten in DynamoDB werden auf Solid-State-Disks (SSDs) gespeichert, automatisch partitioniert und über mehrere Verfügbarkeitszonen repliziert. Außerdem bietet sie eine fein abgestufte Zugriffskontrolle und verwendet bewährte, sichere Methoden, um Nutzer zu authentifizieren und unbefugten Datenzugriff zu verhindern.

DynamoDB, ein von AWS bereitgestellter Dienst, kann nicht auf einem lokalen Server oder in anderen Clouds als AWS installiert werden. Verwende DynamoDB nur, wenn du AWS als primäre Cloud-Infrastruktur für deine Cloud-nativen Anwendungen nutzt. Außerdem verfügt DynamoDB im Vergleich zu relationalen Speichern nur über begrenzte Abfragemöglichkeiten und unterstützt keine relationalen Datenbankfunktionen wie Tabellen-Joins und Fremdschlüsselkonzepte; stattdessen wird die Verwendung von nicht-normalisierten Daten mit Redundanz für die Leistung empfohlen.

Apache HDFS

Das Apache Hadoop Distributed File System(HDFS) ist ein weit verbreitetes verteiltes Dateisystem, das für den Betrieb auf billiger Standard-Hardware entwickelt wurde und gleichzeitig eine hohe Ausfallsicherheit bietet, indem es mindestens drei Kopien der Daten auf verteilte Weise speichert. HDFS wird häufig zur Speicherung von analytischen Daten verwendet, da die Daten in HDFS unveränderlich sind und für das Schreiben und Lesen von Daten im Streaming-Verfahren optimiert sind. Dadurch kann HDFS auch als Datenquelle für Hadoop MapReduce-Aufträge verwendet werden, um große Daten effizient zu verarbeiten. Cloudera und andere große Cloud-Anbieter stellen HDFS als gehosteten Dienst zur Verfügung, der mit Cloud-Anwendungen genutzt werden kann.

HDFS speichert Daten in mehreren Datenknoten und speichert alle Metadaten im Speicher in einem Knoten mit einem Namen. Wenn dieser Knoten nicht verfügbar ist, kann er neue Lese- und Schreibvorgänge fehlschlagen, was zu einer Nichtverfügbarkeit führt. Außerdem gibt es je nach Speicherkapazität des Namensknotens eine Obergrenze für die Anzahl der Dateien, die er speichern kann. Wir empfehlen, HDFS zu verwenden, um eine kleine Anzahl großer Dateien statt einer großen Anzahl kleiner Dateien zu speichern. Da es für das sequentielle Lesen von Daten optimiert ist, ist es nicht die beste Lösung, wenn wir zufällige Lesevorgänge benötigen.

Amazon S3

Amazon Simple Storage Service(S3) ist eine Objektspeicherung, die Teil von AWS ist. Er kann in einem Data Lake, als Speicherung für Cloud-native Anwendungen, als Datensicherung oder -archiv und für Big Data-Analysen verwendet werden. Er unterstützt auch das Data Locality Pattern, indem er Analysen auf Datenknoten mit Standard-SQL Ausdrücken von Amazon Athena ausführt. Wir können S3 Select verwenden, um Teilmengen von Objektdaten abzurufen, anstatt das gesamte Objekt. Dies kann die Leistung beim Datenzugriff um das bis zu Vierfache verbessern. Amazon S3 ist hochverfügbar und bietet eine fein abgestufte Zugriffskontrolle für Daten. Wir empfehlen, es zu nutzen, wenn du AWS als primäre Plattform für native Cloud-Anwendungen verwendest.

Azure Cosmos DB

Azure Cosmos DB ist ein vollständig verwalteter NoSQL Datenspeicher, der die Semantik von Schlüsselwert-, Dokumenten-, Spalten- und Graphdatenbanken unterstützt. Er kann Daten mit geringer Latenzzeit speichern und abrufen und bietet Sicherheit auf Unternehmensniveau mit End-to-End-Verschlüsselung und Zugriffskontrolle. Er bietet außerdem Open-Source-APIs für MongoDB und Cassandra, damit Kunden die Cloud nutzen können, ohne ihre Anwendung zu ändern.

Cosmos DB, ein von Azure bereitgestellter Dienst, kann nicht auf einem lokalen Server oder in anderen Clouds als Azure installiert werden. Nutze Cosmos DB nur, wenn du Azure als primäre Cloud-Infrastruktur für deine Cloud-nativen Anwendungen nutzt. Dennoch bietet Cosmos DB eine gewisse Flexibilität, indem es die Migration und Synchronisation von Daten mit deinem Cassandra-Cluster vor Ort ermöglicht. Cosmos DB bietet zwar Transaktionsunterstützung, ist aber auf die logische Datenpartition beschränkt.

Google Cloud Spanner

Google Cloud Spanner ist ein vollständig verwalteter relationaler Datenspeicher, der unbegrenzte Skalierung und starke Konsistenz unterstützt. Er bietet die Möglichkeit, SQL-Abfragen auszuführen und unterstützt Transaktionen auf allen Knoten im Cluster. Außerdem skaliert er Schreib- und Lesetransaktionen linear und bietet Sicherheit durch Verschlüsselung auf der Datenebene und Zugriffskontrollen.

Da Cloud Spanner ein von Google bereitgestellter Dienst ist, kann er nicht auf einem lokalen Server oder in anderen Clouds als Google installiert werden. Verwende Spanner nur, wenn du Google als primäre Cloud-Infrastruktur für deine nativen Cloud-Anwendungen nutzt. Cloud Spanner bietet zwar SQL-Unterstützung, unterstützt aber die SQL-Spezifikation des American National Standards Institute (ANSI) nicht vollständig und erfordert daher Änderungen an den Anwendungen, bevor du von relationalen Standarddatenbanken zu Spanner migrierst.

Zusammenfassung der Technologien

In diesem Abschnitt wurden einige häufig verwendete Datenspeicher vorgestellt, die wir bei der Entwicklung von Cloud Native Applications verwenden können. Tabelle 4-7 fasst zusammen, wann wir diese Datenspeicher verwenden sollten und wann nicht.

Tabelle 4-7. Typen von Datenspeichern
Typ des Datenspeichers Wann verwendet man Wann man nicht verwenden sollte
Relationales Datenbankmanagementsystem (RDBMS) Du brauchst Transaktionen und ACID-Eigenschaften.
Die Beziehung zu den Daten muss erhalten bleiben.
Sie arbeiten mit kleinen bis mittleren Datenmengen.
Die Daten müssen hoch skalierbar sein, z. B. IoT-Daten.
Arbeit mit XML-, JSON- und Binärdatenformaten.
Die Lösung kann ein gewisses Maß an Nichtverfügbarkeit nicht tolerieren.
Apache Cassandra Du brauchst hohe Verfügbarkeit.
Du brauchst Skalierbarkeit.
Du brauchst eine dezentralisierte Lösung.
Sie müssen schneller schreiben als lesen.
Der Lesezugriff kann meist über den Partitionsschlüssel erfolgen.
Vorhandene Daten werden häufig aktualisiert.
Sie müssen auf Daten über Spalten zugreifen, die nicht Teil des Partitionsschlüssels sind.
Sie benötigen relationale Funktionen, wie Transaktionen, komplexe Joins und ACID-Eigenschaften.
Apache HBase Du brauchst Konsistenz.
Du brauchst Skalierbarkeit.
Du brauchst eine dezentralisierte Lösung.
Du brauchst eine hohe Leseleistung.
Sie brauchen sowohl zufälligen als auch Echtzeit-Zugriff auf Daten.
Sie müssen Petabytes an Daten speichern.
Die Lösung kann ein gewisses Maß an Nichtverfügbarkeit nicht tolerieren.
Vorhandene Daten werden sehr häufig aktualisiert.
Erfordert relationale Funktionen wie Transaktionen, komplexe Joins und ACID-Eigenschaften.
MongoDB Wir brauchen Konsistenz.
Du brauchst eine dezentrale Lösung.
Du brauchst einen Dokumentenspeicher.
Du brauchst eine Datensuche, die auf mehreren Schlüsseln basiert.
Sie brauchen eine hohe Schreibleistung.
Die Lösung kann ein gewisses Maß an Nichtverfügbarkeit nicht tolerieren.
Sie benötigen relationale Funktionen, wie Transaktionen, komplexe Joins und ACID-Eigenschaften.
Redis Du brauchst Skalierbarkeit.
Du brauchst eine In-Memory-Datenbank.
Du brauchst eine persistente Option zur Wiederherstellung der Daten.
Als Cache, Warteschlange und Echtzeit-Speicherung.
Als typische Datenbank zum Speichern und Abfragen mit komplexen Operationen.
Amazon DynamoDB Du brauchst eine hoch skalierbare Lösung.
Du brauchst einen Dokumentenspeicher.
Du brauchst einen Key-Value-Speicher.
Sie brauchen eine hohe Schreibleistung.
Feinkörnige Zugriffskontrolle.
Verwendung auf anderen Plattformen als AWS.
Erfordert relationale Funktionen, wie komplexe Joins und Fremdschlüssel.
Apache HDFS Du brauchst ein Dateisystem.
Große Dateien speichern.
Daten einmal speichern und mehrmals lesen.
MapReduce-Operationen auf Dateien durchführen.
Sie brauchen Skalierbarkeit.
Benötigt Datenausfallsicherheit.
Kleine Dateien speichern.
Dateien aktualisieren müssen.
Zufällige Lesevorgänge durchführen müssen.
Amazon S3 Du brauchst einen Objektspeicher.
MapReduce-Operationen auf Objekten durchführen.
Du brauchst eine hoch skalierbare Lösung.
Einen Teil der Objektdaten lesen.
Feinkörnige Zugriffskontrolle.
Verwendung auf anderen Plattformen als AWS.
Du musst komplexe Abfragen durchführen.
Azure Cosmos DB Du brauchst eine hoch skalierbare Lösung.
Du brauchst einen Dokumentenspeicher.
Du brauchst einen Key-Value-Speicher.
Du brauchst einen Graphenspeicher.
Sie brauchen einen Spaltenspeicher.
Feinkörnige Zugriffskontrolle.
Konnektivität über MongoDB- und Cassandra-Clients
Verwendung auf anderen Plattformen als Azure.
Führen Sie Transaktionen über Datenpartitionen hinweg durch.
Google Cloud Spanner Du brauchst eine hoch skalierbare Lösung.
Du brauchst einen relationalen Speicher.
Unterstützung für die Verarbeitung von SQL-Abfragen benötigen
Du brauchst Transaktionsunterstützung für alle Knoten im Cluster.
Verwendung auf anderen Plattformen als Google Cloud.
Unterstützung für die ANSI-SQL-Spezifikation.

Testen

Das Testen von ist ein wichtiger Schritt für den Aufbau erfolgreicher Cloud Native Applications. Nachdem wir in Kapitel 2 über das Testen von Microservices gesprochen haben, konzentrieren wir uns hier auf das Testen von Datenservices und Datenspeichern.

Wir können Testdatenspeicher verwenden, um die Interaktionen zwischen Datendiensten zu testen. Obwohl Datendienste eine komplexe oder einfache Logik haben können, können sie in der Produktion dennoch Engpässe verursachen. Im Folgenden findest du nützliche Empfehlungen zur Überwindung dieser Probleme:

  • Die Tests sollten sowohl mit sauberen als auch mit vorausgefüllten Datenspeichern durchgeführt werden, da erstere den Dateninitialisierungscode und letztere die Datenkonsistenz während des Betriebs testen.

  • Teste alle Datenspeichertypen und Versionen, die in der Produktion verwendet werden, um Überraschungen auszuschließen. Wir können Testdatenspeicher als Docker-Instanzen implementieren, die dabei helfen, Tests in mehreren Umgebungen mit schnellem Start und ordnungsgemäßem Aufräumen nach dem Test durchzuführen.

  • Teste die Datenzuordnung und stelle sicher, dass alle Felder beim Aufruf des Datenspeichers richtig zugeordnet sind.

  • Überprüfe, ob der Dienst Einfüge-, Schreib-, Lösch- und Aktualisierungsvorgänge in den Datenspeichern in der erwarteten Weise durchführt, indem du den Zustand des Datenspeichers über Testclients überprüfst, die direkt auf die Datenbank zugreifen können.

  • Überprüfe, ob relationale Beschränkungen, Trigger und gespeicherte Prozeduren korrekte Ergebnisse liefern.

Außerdem ist es wichtig, den Datenservice und den Datenspeicher in einer produktionsähnlichen Umgebung mit mehreren Clients einem Lasttest zu unterziehen. So lassen sich eventuelle Datenbanksperren, Datenkonsistenz oder andere leistungsbezogene Engpässe in der nativen Cloud-Anwendung erkennen. Er zeigt auch, wie viel Last die Anwendung bewältigen kann und wie sich das auf die Skalierung der Daten auswirkt, wenn verschiedene Muster und Techniken eingesetzt werden.

Wenn es um das Testen von Cloud-nativen Microservices geht, die von Datendiensten abhängen, können wir einfach Mock-Service-APIs verwenden, um die Datendienste zu mocken und die Bereitstellung von Datenspeichern zu vermeiden.

Sicherheit

Der Schutz der Daten von und der Zugriff auf relevante Daten nur durch die richtigen Personen und Systeme ist der Schlüssel für die erfolgreiche Ausführung einer cloudbasierten Anwendung und für den Erfolg eines Unternehmens im Allgemeinen. Die Sicherheit der Daten sollte sowohl im Ruhezustand als auch unterwegs gewährleistet sein.

Wir können die Datensicherheit im Ruhezustand sowohl physisch als auch durch Software durchsetzen. Datenserver sollten bewacht werden und nur autorisierten Personen zugänglich sein. Datenspeicher, die auf den Servern laufen, sollten auch die Sicherheit über das Vault-Schlüssel-Muster und die API-Sicherheit durchsetzen, um den Datenzugriff zu kontrollieren. Wenn du sensible Daten speicherst, empfehlen wir, sie zu verschlüsseln, bevor du sie im Datenspeicher speicherst. Wir empfehlen außerdem, das Dateisystem, in dem die Daten gespeichert sind, zu verschlüsseln, um einen zusätzlichen Schutz zu gewährleisten.

Wir empfehlen, sensible Daten von anderen Daten zu trennen, damit sensible Daten mit zusätzlichen Schutzebenen geregelt werden können, zusammen mit Prüfpfaden zur Überwachung verdächtigen Verhaltens. Sammle und speichere keine unnötigen sensiblen Daten. Wenn nötig, maskiere alle sensiblen Daten wie Benutzernamen und E-Mail-Adressen. Dies kann geschehen, indem du sensible Daten durch eindeutige Identifikatoren ersetzst und ihre Zuordnung in einem geschützten Datenspeicher speicherst. Auf diese Weise können wir das Nutzerverhalten kontinuierlich analysieren und überprüfen und haben gleichzeitig die Möglichkeit, alle sensiblen Nutzerdaten durch einfaches Löschen der Datenzuordnung zu löschen. Dies hilft auch bei der Durchsetzung des Datenschutzes und von Datenvorschriften wie der europäischen Datenschutzgrundverordnung (GDPR).

Wenn es um die Übertragung von Daten geht, sollten wir die Daten immer über sichere Datenübertragungskanäle wie HTTPS übertragen. Für zusätzliche Sicherheit können wir die Nachrichten mit asymmetrischen Schlüsseln verschlüsseln, damit die zwischengeschalteten Hosts keinen Zugriff auf die Inhalte haben.

Um sensible Daten zu schützen, ohne die Nachricht zu segmentieren, können wir nur den Teil der Nachricht verschlüsseln, der sensible Daten enthält. Die gesamte Nachricht wird an jeden Kunden zugestellt, aber nur die Kunden, die den entsprechenden Schlüssel für die sensiblen Daten haben, können sie entschlüsseln, während andere keinen Zugriff auf diese Daten haben.

Beobachtbarkeit und Überwachung

Beobachtbarkeit und Überwachung ermöglichen es uns, tiefere Einblicke in die Leistung von Cloud-nativen Anwendungen zu gewinnen, indem wir ihre Metriken, Logs und verteilten Tracing-Ergebnisse betrachten. Da die Beobachtbarkeit und Überwachung von Microservices in Kapitel 2 behandelt wird, konzentrieren wir uns hier hauptsächlich auf Datenspeicher.

Beobachtbarkeit und Überwachung helfen uns, die Leistung der Datenspeicher zu erkennen und Korrekturmaßnahmen zu ergreifen, wenn sie aufgrund von Last oder Änderungen in der Anwendung abweichen. Bei den meisten Anwendungen interagieren die eingehenden Anfragen mit den Datenspeichern. Leistungs- oder Verfügbarkeitsprobleme im Datenspeicher wirken sich auf alle Ebenen des Systems aus und beeinträchtigen das gesamte Nutzererlebnis.

Die Überwachung von Datenspeichern ist wichtig, um Probleme mit der Leistung, der Verfügbarkeit und der Sicherheit zu minimieren. Die wichtigsten Metriken, die in einem Datenspeicher zu beobachten sind, sind unter zu finden:

  • Anwendungsmetriken

    • Betriebszeit/Gesundheit des Datenspeichers: Um festzustellen, ob die einzelnen Knoten des Datenspeichers in Betrieb sind und funktionieren.

    • Abfrageausführungszeit: Fünf Arten von Problemen können hohe Ausführungszeiten für Abfragen verursachen:

      • Ineffiziente Abfrage: Verwendung von nicht optimierten Abfragen, einschließlich mehrerer komplexer Joins, und Tabellen, die nicht richtig indiziert sind.

      • Datenwachstum im Datenspeicher: Der Datenspeicher enthält mehr Daten, als er verarbeiten kann.

      • Gleichzeitigkeit: Gleichzeitige Operationen auf derselben Tabelle/Zeile, die Datenspeicher sperren und deren Leistung beeinträchtigen.

      • Mangel an Systemressourcen wie CPU/Speicher/Plattenplatz: Die Knotenpunkte des Datenspeichers verfügen nicht über genügend Ressourcen, um die Anfrage effizient zu bearbeiten.

      • Nichtverfügbarkeit eines abhängigen Systems oder Replikats: Wenn in verteilten Datenspeichern das Replikat oder andere abhängige Systeme, wie z. B. ein Suchdienst, nicht verfügbar sind, kann es mehr Zeit in Anspruch nehmen, da eine neue Instanz bereitgestellt oder die Anfrage an eine andere Instanz weitergeleitet werden muss.

    • Antwort auf die Abfrageausführung: Ob die Ausführung der Abfrage erfolgreich ist. Wenn die Abfrage fehlschlägt, müssen wir uns eventuell die Logs ansehen, um mehr Details zu erfahren (je nach Fehler).

    • Überprüfung der Abfrageoperationen: Böswillige Abfragen oder Benutzeroperationen können zu unerwarteten Leistungseinbußen im Datenspeicher führen. Mithilfe von Audit-Protokollen können wir sie identifizieren und entschärfen.

  • Systemmetriken: Erkennen eines Mangels an Systemressourcen für eine effiziente Verarbeitung über CPU-Verbrauch, Arbeitsspeicherauslastung, Verfügbarkeit von Festplattenplatz, Netzwerkauslastung und Festplatten-E/A-Geschwindigkeit.

  • Datenspeicherprotokolle

  • Zeitaufwand und Durchsatz bei der Kommunikation mit Primär- und Replikaten: Hilft dabei, Netzwerkprobleme und schlechte Datenspeicher zu verstehen

Bei der Analyse der Kennzahlen können wir auf Perzentile verwenden, um historische und aktuelle Verhaltensweisen zu vergleichen. Auf diese Weise lassen sich Anomalien und Abweichungen erkennen, sodass wir die Ursache des Problems schnell identifizieren können. Überwachungs-Tools wie SolarWinds und SQL Power Tools liefern beispielsweise Metriken wie die Ausführungszeit von Abfragen und die Antwortzeit, und Systeme wie Elastic Stack und Kibana analysieren Datenspeicherprotokolle, um deren Zustand und den Grund für Abfrageausfälle zu veranschaulichen. Wenn wir Datenspeicher verwenden, die von Cloud-Anbietern wie Google Cloud, AWS oder Azure verwaltet werden, bieten auch sie Überwachungsdienste an, um System- und Datenspeichermetriken zu überwachen.

DevOps

Wir haben unter verschiedene Datenverwaltungsmuster besprochen, die in Cloud-nativen Anwendungen sowohl mit Microservices als auch mit Datenspeichern angewendet werden können. In Kapitel 2 haben wir bereits den DevOps-Prozess für die Bereitstellung und Verwaltung von Microservices besprochen, daher konzentrieren wir uns hier auf die Bereitstellung und Verwaltung von Datenspeichern.

Die Schritte und wichtigsten Überlegungen für die Einrichtung und Verwaltung von Datenspeichern sind wie folgt:

  1. Wähle die Datenspeichertypen aus. Wähle den Typ des Datenspeichers (relational, NoSQL oder Dateisystem) und den Anbieter, der zu unserem Anwendungsfall passt.

  2. Konfiguriere das Bereitstellungsmuster. Dies kann von den Mustern beeinflusst werden, die in der nativen Cloud-Anwendung angewendet werden, und von der Art des Datenspeichers, den wir ausgewählt haben. Auf der Grundlage dieser Auswahl sollten Hochverfügbarkeit und Skalierbarkeit durch die Beantwortung der folgenden Fragen bestimmt werden:

    • Wer sind die Kunden?

    • Wie viele Knotenpunkte?

    • Werden wir einen vom Cloud-Anbieter verwalteten Datenspeicher nutzen oder unseren eigenen einrichten?

    • Wie funktioniert die Replikation?

    • Wie sichern wir die Daten?

    • Wie funktioniert die Notfallwiederherstellung?

    • Wie sichern wir den Datenspeicher?

    • Wie überwachen wir den Datenspeicher?

    • Wie viel kostet die Datenspeicherung/-verwaltung?

  3. Sicherheit durchsetzen. Datenspeicher sollten geschützt werden, da sie geschäftskritische Informationen enthalten. Dies kann durch entsprechende physische und softwareseitige Sicherheitsvorkehrungen erreicht werden, wie im vorangegangenen Abschnitt beschrieben. Dazu gehören z. B. strenge Zugriffskontrollen, Datenverschlüsselung und die Verwendung von Prüfprotokollen.

  4. Beobachtbarkeit und Überwachung einrichten. Wie Microservices sollten auch Datenspeicher mit Beobachtungs- und Überwachungs-Tools konfiguriert werden, um einen kontinuierlichen Betrieb zu gewährleisten. Dies kann frühzeitig Aufschluss über mögliche Skalierungsprobleme geben, z. B. über die Notwendigkeit, Daten-Shards neu zu balancieren oder ein anderes Designmuster anzuwenden, um die Skalierbarkeit und Leistung der Anwendung zu verbessern.

  5. Automatisiere die kontinuierliche Bereitstellung. Wenn es um Datenspeicher geht, sind Automatisierung und kontinuierliche Bereitstellung nicht ganz einfach. Auch wenn wir ein anfängliches Datenspeicherschema leicht erstellen können, ist es schwierig, die Abwärtskompatibilität aufrechtzuerhalten, wenn sich die Anwendung weiterentwickelt. Die Abwärtskompatibilität ist von entscheidender Bedeutung, denn ohne sie können wir keine reibungslosen Anwendungsaktualisierungen und Rollbacks bei Ausfällen durchführen. Um die Produktivität zu verbessern, sollten wir immer geeignete Automatisierungstools wie Skripte verwenden, um die kontinuierliche Bereitstellung zu automatisieren. Außerdem empfehlen wir Leitplanken und die Verwendung mehrerer Bereitstellungsumgebungen, z. B. Entwicklungs- und Vorproduktionsumgebungen, um die Auswirkungen der Änderungen zu verringern und die Anwendung zu validieren, bevor sie in die Produktion überführt wird.

Wenn wir diese Schritte befolgen, können wir Cloud-native Anwendungen sicher einsetzen und warten und gleichzeitig schnelle Innovationen und die Übernahme in andere Systeme ermöglichen.

Zusammenfassung

In diesem Kapitel haben wir verschiedene Datenverwaltungsmuster besprochen, die auf Cloud-native Anwendungen angewendet werden können. Wir haben mit einem Überblick über die Datenarchitektur begonnen und uns dann verschiedene Datentypen wie Eingabe-, Konfigurations- und Zustandsdaten angesehen, die das Verhalten der Anwendung beeinflussen können. Außerdem haben wir uns mit strukturierten, halbstrukturierten und unstrukturierten Daten befasst und erklärt, wie wir sie in verschiedenen Datenspeichern wie relationalen, NoSQL- und Dateisystemen effizient speichern und verwalten können.

Anschließend haben wir erörtert, wie diese Daten verwaltet und von nativen Cloud-Anwendungen gemeinsam genutzt werden können und wie verschiedene Entwurfsmuster verwendet werden können, um Datenkomposition, Datenskalierung, Leistungsoptimierung, Zuverlässigkeit und Sicherheit zu erreichen. Außerdem haben wir uns verschiedene Technologien für das Datenmanagement angeschaut und wie datenzentrierte Cloud Native Applications entwickelt, getestet, mithilfe von DevOps kontinuierlich bereitgestellt sowie beobachtet und überwacht werden sollten, um einen kontinuierlichen Betrieb zu gewährleisten. Als Nächstes werden wir uns mit Mustern für ereignisgesteuerte Cloud-Native-Anwendungen beschäftigen.

Get Design Patterns für Cloud Native Anwendungen 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.