Hallo zusammen. Hier ist Wladislaw Rodin. Momentan unterrichte ich auf dem Portal OTUS Kurse, die sich mit Softwarearchitektur und Hochlast-Softwarearchitektur beschĂ€ftigen. In der Vorbereitungsphase fĂŒr den Start eines neuen Kurses habe ich beschlossen, ein kleines eigenes Material zu schreiben, das ich mit Ihnen teilen möchte.

EinfĂŒhrung
Da auf einer HDD nur etwa 400-700 Operationen pro Sekunde ausgefĂŒhrt werden können (was im Vergleich zu typischen rps, die auf ein hochbelastetes System entfallen, unvorstellbar ist), ist eine klassische Datenbank ein Engpass in der Architektur. Daher ist es notwendig, besondere Aufmerksamkeit den Skalierungsmustern dieses Speichers zu widmen.
Derzeit gibt es 2 Skalierungsmuster fĂŒr die Datenbank: Replikation und Sharding. Sharding ermöglicht es, die Schreiboperation zu skalieren und somit die rps beim Schreiben, die auf einen Server Ihres Clusters entfallen, zu reduzieren. Die Replikation ermöglicht dasselbe, jedoch fĂŒr Leseoperationen. Genau diesem Muster ist dieser Artikel gewidmet.
Replikation
Wenn man sich die Replikation ganz allgemein ansieht, ist es eine einfache Sache: Sie hatten einen Server, auf dem die Daten lagen, und dann konnte dieser Server die Leseanforderungen dieser Daten nicht mehr bewĂ€ltigen. Sie fĂŒgen ein paar Server hinzu, synchronisieren die Daten auf allen Servern, und der Benutzer kann von jedem Server Ihres Clusters lesen.
Trotz der scheinbaren Einfachheit gibt es mehrere Klassifizierungsmöglichkeiten fĂŒr verschiedene Implementierungen dieses Schemas:
- Nach Rollen im Cluster (Master-Master oder Master-Slave)
- Nach ĂŒbermittelten Objekten (zeilenbasiert, statements-basiert oder gemischt)
- Nach dem Mechanismus zur Synchronisation der Knoten
Heute werden wir uns genau mit dem 3. Punkt beschÀftigen.
Wie der Commit einer Transaktion ablÀuft
Dieses Thema gehört nicht direkt zur Replikation, darĂŒber könnte ein separater Artikel geschrieben werden, aber da das VerstĂ€ndnis des Commit-Mechanismus fĂŒr das weitere Lesen unerlĂ€sslich ist, erlaube ich mir, die grundlegendsten Dinge in Erinnerung zu rufen. Der Commit einer Transaktion erfolgt in 3 Phasen:
- Aufzeichnung der Transaktion im Datenbankprotokoll.
- Anwendung der Transaktion im Datenbank-Engine.
- RĂŒcksendung der BestĂ€tigung an den Kunden ĂŒber die erfolgreiche Anwendung der Transaktion.
In verschiedenen Datenbanken können in diesem Algorithmus Nuancen auftreten: Zum Beispiel gibt es im InnoDB-Engine von MySQL 2 Protokolle: eines fĂŒr die Replikation (binary log) und ein anderes zur Aufrechterhaltung von ACID (undo/redolog), wĂ€hrend PostgreSQL ein Protokoll hat, das beide Funktionen erfĂŒllt (write ahead log = WAL). Aber oben wurde genau das allgemeine Konzept vorgestellt, das solche Nuancen unberĂŒcksichtigt lĂ€sst.
Synchronisierte (sync) Replikation
Lassen Sie uns dem Commit-Algorithmus der Transaktion Logik zur Replikation der erhaltenen Ănderungen hinzufĂŒgen:
- Aufzeichnung der Transaktion im Datenbankprotokoll.
- Anwendung der Transaktion im Datenbank-Engine.
- Daten an alle Replikate senden.
- BestĂ€tigung von allen Replikaten ĂŒber die DurchfĂŒhrung der Transaktion erhalten.
- RĂŒcksendung der BestĂ€tigung an den Kunden ĂŒber die erfolgreiche Anwendung der Transaktion.
Bei diesem Ansatz ergeben sich einige Nachteile:
- der Client wartet, bis die Ănderungen auf allen Replikaten angewendet werden.
- Mit zunehmender Anzahl der Nodes im Cluster verringern wir die Wahrscheinlichkeit, dass die Schreiboperation erfolgreich abgeschlossen wird.
Wenn der erste Punkt mehr oder weniger klar ist, sollte der zweite Punkt erlĂ€utert werden. Wenn bei synchroner Replikation keine Antwort von mindestens einem Node vorliegt, rollen wir die Transaktion zurĂŒck. So erhöht man, indem man die Anzahl der Nodes im Cluster vergröĂert, die Wahrscheinlichkeit, dass die Schreiboperation fehlschlĂ€gt.
Können wir nur auf eine bestimmte Anzahl von Nodes, zum Beispiel 51% (Quorum), eine BestÀtigung erwarten? Ja, das können wir, jedoch erfordert es im klassischen Szenario die BestÀtigung von allen Nodes, da wir nur so die vollstÀndige Konsistenz der Daten im Cluster gewÀhrleisten können, was unbestreitbar ein Vorteil dieser Replikationsart ist.
Asynchrone (async) Replikation
Lassen Sie uns den vorherigen Algorithmus abwandeln. Wir werden die Daten an die Replikate "irgendwann spĂ€ter" senden, und "irgendwann spĂ€ter" werden die Ănderungen auf den Replikaten angewendet:
- Aufzeichnung der Transaktion im Datenbankprotokoll.
- Anwendung der Transaktion im Datenbank-Engine.
- RĂŒcksendung der BestĂ€tigung an den Kunden ĂŒber die erfolgreiche Anwendung der Transaktion.
- Daten an die Replikate senden und Ănderungen anwenden.
Dieser Ansatz fĂŒhrt dazu, dass der Cluster schnell arbeitet, da wir den Client nicht warten lassen, bis die Daten zu den Replikaten gelangen und auch noch festgeschrieben werden.
Aber die Bedingung, Daten "irgendwann spĂ€ter" auf die Replikate zu ĂŒbertragen, kann dazu fĂŒhren, dass Transaktionen verloren gehen, insbesondere bestĂ€tigte Transaktionen fĂŒr den Benutzer, denn wenn die Daten nicht rechtzeitig repliziert wurden, wird die BestĂ€tigung an den Client ĂŒber den erfolgreichen Abschluss der Operation gesendet, und wenn das HDD des Nodes, an den die Ănderungen gesendet wurden, ausfĂ€llt, verlieren wir die Transaktion, was zu sehr unangenehmen Folgen fĂŒhren kann.
Halbsynchrone (semisync) Replikation
Endlich haben wir die semi-synchrone Replikation erreicht. Diese Art der Replikation ist nicht sehr bekannt und nicht weit verbreitet, bietet jedoch ein gewisses Interesse, da sie die Vorteile sowohl der synchronen als auch der asynchronen Replikation kombinieren kann.
Lass uns die beiden vorherigen AnsÀtze kombinieren. Wir werden den Client nicht lange warten lassen, verlangen jedoch, dass die Daten repliziert werden:
- Aufzeichnung der Transaktion im Datenbankprotokoll.
- Anwendung der Transaktion im Datenbank-Engine.
- Daten an die Replikate senden.
- Erhalt einer BestĂ€tigung von der Replik ĂŒber den Erhalt der Ănderungen (diese werden "irgendwann spĂ€ter" angewendet).
- RĂŒcksendung der BestĂ€tigung an den Kunden ĂŒber die erfolgreiche Anwendung der Transaktion.
Beachte, dass bei diesem Algorithmus der Verlust einer Transaktion nur dann auftritt, wenn sowohl der Knoten, der die Ănderungen akzeptiert, als auch der Replikationsknoten ausfallen. Die Wahrscheinlichkeit eines solchen Ausfalls wird als gering eingeschĂ€tzt, und diese Risiken werden akzeptiert.
Bei diesem Ansatz besteht jedoch das Risiko von Phantomlesen. Stellen wir uns folgendes Szenario vor: In Schritt 4 haben wir von keiner Replik eine BestĂ€tigung erhalten. Wir mĂŒssen diese Transaktion zurĂŒcksetzen, ohne dem Client eine BestĂ€tigung zurĂŒckzugeben. Da die Daten in Schritt 2 angewendet wurden, entsteht zwischen dem Ende von Schritt 2 und dem ZurĂŒcksetzen der Transaktion ein Zeitfenster, in dem parallele Transaktionen die Ănderungen sehen könnten, die in der Datenbank nicht vorhanden sein sollten.
Lose-less semi-synchrone Replikation
Wenn man ein wenig nachdenkt, kann man durch einfaches Vertauschen der Schritte im Algorithmus das Problem der Phantomlesungen in diesem Szenario beheben:
- Aufzeichnung der Transaktion im Datenbankprotokoll.
- Daten an die Replik senden.
- Erhalt einer BestĂ€tigung von der Replik ĂŒber den Erhalt der Ănderungen (diese werden "irgendwann spĂ€ter" angewendet).
- Anwendung der Transaktion im Datenbank-Engine.
- RĂŒcksendung der BestĂ€tigung an den Kunden ĂŒber die erfolgreiche Anwendung der Transaktion.
Jetzt bestĂ€tigen wir die Ănderungen nur, wenn sie repliziert wurden.
Ausgabe
Wie immer gibt es keine perfekten Lösungen, sondern eine Reihe von Lösungen, von denen jede ihre eigenen Vor- und Nachteile hat und fĂŒr die Lösung verschiedener Klassen von Aufgaben geeignet ist. Dies gilt auch fĂŒr die Wahl des Mechanismus zur Synchronisierung der Daten der replizierten Datenbank. Das Set an Vorteilen, das die semi-synchrone Replikation bietet, ist ziemlich solide und interessant genug, um als bemerkenswert eingestuft zu werden, trotz ihrer geringen Verbreitung.
Das wÀre alles. Bis zum nÀchsten Mal bei !
Quelle: habr.com
