Wie zĂ€hle ich google.com-Seitentreffer? Und wie speichert man den ZĂ€hler von Likes sehr beliebter Benutzer? In diesem Artikel wird vorgeschlagen, die Lösung dieser Probleme mithilfe von CRDT (Conflict-Free Replicated Data Types, was auf Russisch ungefĂ€hr als Conflict-Free Replicated Data Types ĂŒbersetzt wird) und im allgemeineren Fall von Replikatsynchronisierungsaufgaben in einem verteilten System mit mehreren fĂŒhrenden Knoten in Betracht zu ziehen.
1. Einleitung
Wir sind seit langem daran gewöhnt, Anwendungen wie einen Kalender oder einen Notendienst wie Evernote zu verwenden. Sie zeichnen sich dadurch aus, dass Sie offline (von mehreren GerĂ€ten bis zu mehreren Personen gleichzeitig (mit denselben Daten) arbeiten können. Die Herausforderung fĂŒr die Entwickler jeder solchen Anwendung besteht darin, die âreibungslosesteâ Synchronisierung von Daten sicherzustellen, die gleichzeitig auf mehreren GerĂ€ten geĂ€ndert werden. Im Idealfall sollte keine Benutzerbeteiligung erforderlich sein, um ZusammenfĂŒhrungskonflikte zu lösen.
In einem
frĂŒheren Artikel haben wir bereits einen Ansatz zur Lösung solcher Probleme in Betracht gezogen - Operational Transformation. AuĂerdem wird eine sehr Ă€hnliche Methode beschrieben, die sowohl Vor- als auch Nachteile hat (zum Beispiel wurde CRDT fĂŒr JSON noch nicht erfunden. Aktualisierung: Dank an
msvn fĂŒr den Link hier
Hier ist ein Projekt der Autoren eines Forschungsartikels zur Implementierung von JSON in CRDT.
2. Starke eventuelle Konsistenz
In letzter Zeit wurde viel Arbeit geschrieben und viel Forschung auf dem Gebiet der eventuellen Konsistenz betrieben. Meiner Meinung nach gibt es jetzt einen starken Trend hin zu einer Verlagerung von starker Konsistenz zu verschiedenen Optionen fĂŒr Konsistenz, um zu untersuchen, welche Konsistenz in welchen Situationen / Systemen rentabler anzuwenden ist, und um bestehende Definitionen zu ĂŒberdenken. Dies fĂŒhrt zu Verwirrung, wenn beispielsweise die Autoren einiger Werke, die ĂŒber Konsistenz sprechen, eine eventuelle Konsistenz mit einigen zusĂ€tzlichen Eigenschaften bedeuten und andere Autoren hierfĂŒr eine bestimmte Terminologie verwenden.
Die von den Autoren eines der Artikel aufgeworfene Frage kritisiert die derzeitige Definition der eventuellen Konsistenz: Wenn Ihr System auf alle Anfragen immer mit â42â antwortet, ist alles in Ordnung, und es ist schlieĂlich konsistent.
Ohne die Richtigkeit dieses Artikels zu verletzen, verwende ich gemÀà den Autoren der Originalartikel die folgende Terminologie (bitte beachten Sie, dass dies keine strengen Definitionen sind, sondern Unterschiede):
- Starke Konsistenz (SC): Alle SchreibvorgĂ€nge sind streng geordnet. Eine Leseanforderung auf einem Replikat gibt das gleiche, zuletzt aufgezeichnete Ergebnis zurĂŒck. Echtzeitkonsens ist erforderlich, um Konflikte zu lösen (mit den daraus resultierenden Konsequenzen), kann einem RĂŒckgang auf n / 2 - 1 Knoten standhalten.
- Eventuelle Konsistenz (EC): Aktualisieren Sie die Daten lokal und senden Sie das Update weiter. Das Lesen auf verschiedenen Replikaten kann veraltete Daten zurĂŒckgeben. Im Falle von Konflikten rollen wir entweder zurĂŒck oder entscheiden irgendwie, was zu tun ist. T.O. Konsens ist weiterhin erforderlich, jedoch nicht mehr in Echtzeit .
- Starke eventuelle Konsistenz (SEC): EC + zur Lösung von Konflikten haben Replikate einen vordefinierten Algorithmus. T.O. Konsens ist nicht erforderlich , er kann einem Abfall auf n - 1 Knoten standhalten.
Beachten Sie, dass SEC (sozusagen) das Problem des CAP-Theorems löst: Alle drei Eigenschaften sind erfĂŒllt.
Wir sind also bereit, SC zu spenden, und möchten einen bestimmten Satz grundlegender Datentypen fĂŒr unser möglicherweise instabiles verteiltes System haben, die Schreibkonflikte fĂŒr uns automatisch lösen (keine Benutzerinteraktion oder Anforderung an einen Schiedsrichter erforderlich).
3. Aufgaben zu Likes und Hits
Zweifellos gibt es mehrere Algorithmen zur Lösung solcher Probleme. CRDT bietet einen ziemlich eleganten und einfachen Weg.
Google.com Trefferanzahl:
google.com verarbeitet ungefĂ€hr 150.000 Anfragen pro Sekunde aus der ganzen Welt. Offensichtlich muss der ZĂ€hler asynchron aktualisiert werden. Warteschlangen lösen das Problem teilweise. Wenn wir beispielsweise eine externe API bereitstellen, um diesen Wert abzurufen, mĂŒssen wir eine Replikation durchfĂŒhren, um das Repository nicht mit Leseanforderungen zu versehen. Und wenn es bereits eine Replikation gibt, vielleicht ohne globale Warteschlangen?
Benutzerlikes zÀhlen:
Die Aufgabe ist der vorherigen sehr Ă€hnlich, nur dass Sie jetzt eindeutige Treffer zĂ€hlen mĂŒssen.
4. Terminologie
FĂŒr ein umfassenderes VerstĂ€ndnis des Artikels mĂŒssen Sie die folgenden Begriffe kennen:
- Idempotenz
Sagt, dass das mehrmalige Anwenden der Operation das Ergebnis nicht Àndert.
Beispiele - GET-Operation oder Addition mit Null:
- KommutativitÀt
- Teilbestellung
ReflexivitÀt + TransitivitÀt + Antisymmetrie
- Halbgitter
Teilweise bestelltes Set mit exakter Oberseite (Unterseite)
- Versionsvektor
Ein Dimensionsvektor ist gleich der Anzahl der Knoten, und jeder Knoten erhöht bei Auftreten eines bestimmten Ereignisses seinen Wert im Vektor. WĂ€hrend der Synchronisation werden Daten mit diesem Vektor ĂŒbertragen, und dies fĂŒhrt eine Ordnungsbeziehung ein, mit der Sie bestimmen können, welches Replikat alte / neue Daten enthĂ€lt.
5. Modelle synchronisieren
Staatsbasiert:
Es wird auch als passive Synchronisation bezeichnet und bildet den konvergenten replizierten Datentyp - CvRDT.
Es wird in Dateisystemen wie NFS, AFS, Coda und in KV-Speichern Riak, Dynamo verwendet
In diesem Fall tauschen die Replikate den Status direkt aus, das empfangende Replikat fĂŒhrt den empfangenen Status mit seinem aktuellen Status zusammen.
Um die Konvergenz von Replikaten mithilfe dieser Synchronisierung durchzufĂŒhren, ist Folgendes erforderlich:
- Die Daten bildeten ein Halbgitter
- Die ZusammenfĂŒhrungsfunktion ergab eine genaue Obergrenze
- Die Repliken bildeten einen zusammenhÀngenden Graphen.
Ein Beispiel:
- Datensatz: natĂŒrliche Zahlen
- Mindestartikel:
Solche Anforderungen geben uns eine
kommutative und
idempotente ZusammenfĂŒhrungsfunktion, die auf einem bestimmten Datensatz
monoton wÀchst :
Dies stellt sicher, dass die Replikate frĂŒher oder spĂ€ter konvergieren und Sie sich keine Gedanken ĂŒber das DatenĂŒbertragungsprotokoll machen mĂŒssen.
Wir können Nachrichten mit einem neuen Status verlieren, sie mehrmals senden und sie sogar in beliebiger Reihenfolge senden .
Betriebsbasiert:
Es wird auch als aktive Synchronisation bezeichnet und bildet den kommutativen replizierten Datentyp - CmRDT.
Wird in kooperativen Systemen wie Bayou, Rover, IceCube, Telex verwendet.
In diesem Fall tauschen die Replikate StatusaktualisierungsvorgĂ€nge aus. Beim Aktualisieren von Daten wird das ursprĂŒngliche Replikat:
- Ruft die generate () -Methode auf, die die effector () -Methode zurĂŒckgibt, die auf anderen Replikaten ausgefĂŒhrt werden soll. Mit anderen Worten, effector () ist der Abschluss zum Ăndern des Status der verbleibenden Replikate.
- Anwenden eines Effektors auf einen lokalen Staat
- Sendet den Effektor an alle anderen Repliken
Um eine Konvergenz von Replikaten durchzufĂŒhren, mĂŒssen die folgenden Bedingungen erfĂŒllt sein:
- ZuverlÀssiges Lieferprotokoll
- Wenn der Effektor gemÀà der eingegebenen Reihenfolge (fĂŒr einen bestimmten Typ) an alle Replikate geliefert wird, sind simultane Effektoren kommutativ oder
- Wenn der Effektor an alle Replikate geliefert wird, ohne die Bestellung zu berĂŒcksichtigen, sind alle Effektoren kommutativ.
- Wenn der Effektor mehrmals geliefert werden kann, muss er idempotent sein
- Einige Implementierungen verwenden Warteschlangen (Kafka) als Teil des Ăbermittlungsprotokolls.
Delta-basiert:
In Anbetracht des Status / op-basiert ist leicht zu erkennen, dass es keinen Sinn macht, den gesamten Status zu senden, wenn ein Update nur einen Teil des Status Ă€ndert. Wenn eine groĂe Anzahl von Ănderungen einen Status (z. B. einen ZĂ€hler) betrifft, können Sie eine aggregierte Ănderung und nicht alle VorgĂ€nge senden Ănderungen.
Die Delta-Synchronisation kombiniert beide AnsĂ€tze und sendet Delta-Mutatoren aus, die den Status gemÀà dem letzten Synchronisationsdatum aktualisieren. Bei der anfĂ€nglichen Synchronisation muss der Status vollstĂ€ndig gesendet werden, und einige Implementierungen berĂŒcksichtigen in solchen FĂ€llen bereits den Status der verbleibenden Replikate, wenn Delta-Mutatoren erstellt werden.
Die nÀchste Optimierungsmethode besteht darin, das op-basierte Protokoll zu komprimieren, wenn Verzögerungen zulÀssig sind.
Rein betriebsbasiert:
Es gibt eine Verzögerung beim Erstellen eines Opektors bei der op-basierten Synchronisation. In einigen Systemen ist dies möglicherweise nicht akzeptabel. Dann mĂŒssen Sie die ursprĂŒngliche Ănderung auf Kosten der Komplikation des Protokolls und der zusĂ€tzlichen Menge an Metadaten senden.
StandardanwendungsansÀtze:
- Wenn Updates sofort im System gesendet werden sollen, ist zustandsbasiert eine schlechte Wahl, da das Versenden des gesamten Status teurer ist als nur ein Update-Vorgang. Delta-basiert funktioniert besser, aber in diesem speziellen Fall ist der Unterschied zu zustandsbasiert gering.
- Wenn Sie das Replikat nach einem Fehler synchronisieren mĂŒssen, sind zustandsbasiert und deltabasiert die perfekte Wahl. Wenn Sie op-based verwenden mĂŒssen, stehen folgende Optionen zur VerfĂŒgung:
1) Wirf alle verpassten Operationen ab dem Moment des Ausfalls
2) Eine vollstÀndige Kopie einer der Replikate und ein Rollback verpasster VorgÀnge - Wie oben erwÀhnt, erfordert op-based, dass Updates genau einmal an jedes Replikat gesendet werden. Die Lieferanforderung kann nur einmal weggelassen werden, wenn der Effektor idempotent ist. In der Praxis ist es viel einfacher, die erste als die zweite zu implementieren.
Die Beziehung zwischen Op-basiert und State-basiert:
Zwei AnsÀtze können durcheinander emuliert werden, daher werden wir in Zukunft CRDT ohne Bezugnahme auf ein bestimmtes Synchronisationsmodell betrachten.
6. CRDT
6.1 ZĂ€hler
Eine Ganzzahl, die zwei Operationen unterstĂŒtzt: inc und dec. Betrachten Sie als Beispiel mögliche Implementierungen fĂŒr op- und zustandsbasierte Synchronisationen:
Op-basierter ZĂ€hler:
NatĂŒrlich nur Updates senden. Beispiel fĂŒr inc:
function generator() { return function (counter) { counter += 1 } }
StaatszÀhler:
Die Implementierung ist nicht mehr so ââoffensichtlich, da unklar ist, wie die ZusammenfĂŒhrungsfunktion aussehen soll.
Betrachten Sie die folgenden Optionen:
Monoton ansteigender ZÀhler (nur InkrementzÀhler, G-ZÀhler):Die Daten werden als Vektor mit einer Dimension gespeichert, die der Anzahl der Knoten (Versionsvektor) entspricht, und jedes Replikat erhöht den Positionswert mit seiner ID.
Die ZusammenfĂŒhrungsfunktion nimmt an den entsprechenden Positionen ein Maximum ein, und der Endwert ist die Summe aller Elemente des Vektors
\ begin {align} inc () &: V [id ()] = V [id ()] + 1 \\ value () &: \ sum_ {i = 0} ^ {n} V [i] \\ Merge (C_1, C_2) &: i \ in [1..n] \ Ergebnis [i] = max (C_1.V [i], C_2.V [i]) \ end {align}
Sie können auch das G-Set verwenden (siehe unten)
Anwendung:
- Klicks / Treffer zÀhlen (sic!)
ZĂ€hler mit DekrementunterstĂŒtzung (PN-ZĂ€hler)Wir starten zwei G-ZĂ€hler - einen fĂŒr Inkrementierungsoperationen, den zweiten - fĂŒr Dekrementierung
Anwendung:
- Die Anzahl der angemeldeten Benutzer in einem P2P-Netzwerk, z. B. Skype
Nicht negativer ZĂ€hlerEine einfache Implementierung existiert noch nicht. Schlagen Sie Ihre Ideen in den Kommentaren vor, diskutieren Sie.
6.2 Registrieren
Eine Speicherzelle mit zwei Operationen - Zuweisen (Schreiben) und Wert (Lesen).
Das Problem ist, dass die Zuweisung nicht kommutativ ist. Es gibt zwei AnsÀtze, um dieses Problem zu lösen:
Last-Write-Wins-Register (LWW-Register):
Wir geben die vollstĂ€ndige Reihenfolge durch die Generierung einer eindeutigen ID fĂŒr jede Operation ein (z. B. Zeitstempel).
Ein Beispiel fĂŒr die Synchronisation ist der Austausch von Paaren (Wert, ID):
Anwendung:
- Spalten in Cassandra
- NFS - Datei ganz oder teilweise
Register mit mehreren Werten (Multi-Value Register, MV-Register):
Der Ansatz Ă€hnelt einem G-ZĂ€hler - wir speichern die Menge (Wert, Versionsvektor). Wert registrieren - alle Werte beim ZusammenfĂŒhren - LWW separat fĂŒr jeden Wert im Vektor.
Anwendung:
- Korb in Amazon. Damit ist ein bekannter Fehler verbunden, der nach dem Entfernen eines Artikels aus dem Warenkorb dort erneut angezeigt wird. Der Grund ist, dass das Register trotz der Tatsache, dass es eine Reihe von Werten speichert, keine Menge ist (siehe Abbildung unten). Amazon betrachtet dies ĂŒbrigens nicht einmal als Fehler - es steigert sogar den Umsatz.
- Riak. In einem allgemeineren Fall verschieben wir das Problem der Auswahl des tatsÀchlichen Werts (Hinweis - es gibt keinen Konflikt!) Auf die Anwendung.
ErklÀrung des Fehlers in Amazon:
6.3 Lose
Die Menge ist der Grundtyp fĂŒr die Erstellung von Containern, Karten und Diagrammen und unterstĂŒtzt Operationen - add und rmv, die nicht kommutativ sind.
Betrachten Sie die naive Implementierung der op-basierten Menge, in der add und rmv ausgefĂŒhrt werden, sobald sie eintreffen (add kommt gleichzeitig zu 1 und 2 Replikaten, dann geht rmv zu 1).
Wie Sie sehen können, haben sich die Repliken schlieĂlich aufgelöst. Betrachten Sie die verschiedenen Optionen zum Erstellen konfliktfreier Mengen:
Wachsendes Set (G-Set):
Die einfachste Lösung besteht darin, zu verhindern, dass Elemente gelöscht werden. Alles, was bleibt, ist die Kommutierungsoperation, die kommutativ ist. Die ZusammenfĂŒhrungsfunktion ist die Vereinigung von Mengen.
Zweiphasenset (2P-Set):
Wir erlauben Ihnen das Löschen, aber Sie können es nach dem Entfernen nicht wieder hinzufĂŒgen. Zur Implementierung richten wir einen separaten Satz entfernter G-Set-Elemente ein (ein solcher Satz wird als Tombstone-Satz bezeichnet).
Beispiel fĂŒr zustandsbasiert:
\ begin {align} lookup (e) &: e \ in A \ land e \ notin R \\ add (e) &: A = A \ cup \ {e \} \\ rmv (e) &: R = R \ cup \ {e \} \\ zusammenfĂŒhren (S_1, S_2) &: \\ Res & ult.A = S_1.A \ cup S_2.A \\ Res & ult.R = S_1.R \ cup S_2.R \ end {align}
LWW-Element Set:
Die nĂ€chste Möglichkeit, eine konfliktfreie Menge zu implementieren, besteht darin, eine vollstĂ€ndige Reihenfolge einzufĂŒhren. Eine der Optionen besteht darin, fĂŒr jedes Element eindeutige Zeitstempel zu generieren.
Wir erhalten zwei Mengen - add-set und remove-set, wenn add () aufgerufen wird, add (element, unique_id ()), wenn ĂŒberprĂŒft wird, ob sich ein Element in der Menge befindet - schauen Sie, wo der Zeitstempel gröĂer ist - in remove-set oder in add-set
PN-Set:
Variation mit der Reihenfolge der Menge - wir starten einen ZĂ€hler fĂŒr jedes Element, wenn wir es hinzufĂŒgen, erhöhen wir es, wenn wir es löschen, verringern wir es. Ein Element befindet sich in der Menge, wenn sein ZĂ€hler positiv ist.
Beachten Sie den interessanten Effekt: In der dritten Replik fĂŒhrt das HinzufĂŒgen eines Elements nicht zu dessen Erscheinungsbild.
Beobachten-Entfernen-Set, ODER-Set, Add-Win-Set:
Bei diesem Typ hat HinzufĂŒgen Vorrang vor Entfernen. Implementierungsbeispiel: Jedem neu hinzugefĂŒgten Element wird ein eindeutiges Tag zugewiesen (relativ zum Element und nicht zum gesamten Satz). Rmv entfernt ein Element aus der Menge und sendet alle gesehenen Paare (Element, Tag) zur Entfernung an die Replikate.
Remove-Win-Set:
Ăhnlich wie beim vorherigen, aber gleichzeitig gewinnt / rmv rmv.
6.4 Grafik
Dieser Typ basiert auf vielen. Das Problem ist folgendes: Wenn gleichzeitig Operationen addEdge (u, v) und removeVertex (u) ausgefĂŒhrt werden - was soll ich tun? Folgende Optionen sind möglich:
- RemoveVertex-PrioritÀt, alle Kanten, die auf diesen Scheitelpunkt fallen, werden gelöscht
- AddEdge-PrioritÀt, gelöschte Scheitelpunkte wiederhergestellt
- Wir verzögern die AusfĂŒhrung von removeVertex, bis alle gleichzeitigen addEdge ausgefĂŒhrt werden.
Die einfachste Option ist die erste, fĂŒr die Implementierung (2P2P-Graph) reicht es aus, zwei 2P-Sets zu erhalten, eines fĂŒr die Eckpunkte und das zweite fĂŒr die Kanten
6.5 Karte
Karte der Literale:
Zwei zu lösende Probleme:
- Was tun bei gleichzeitigen Put-Operationen? In Analogie zu ZÀhlern können Sie entweder LWW- oder MV-Semantik wÀhlen
- Was tun mit simultan put / rmv? In Analogie zu Sets können Sie entweder Put-Wins- oder RMV-Wins- oder Last-Put-Wins-Semantik verwenden.
CRDT-Zuordnung (Karte der CRDTs):
Ein interessanter Fall, weil Ermöglicht das Erstellen verschachtelter Zuordnungen. FĂ€lle, in denen verschachtelte Typen geĂ€ndert werden, werden nicht berĂŒcksichtigt. Dies sollte vom verschachtelten CRDT selbst entschieden werden.
Karte als rekursiv zurĂŒcksetzen entfernenDie Entfernungsoperation "setzt" den Typwert auf einen bestimmten Startzustand zurĂŒck. FĂŒr einen ZĂ€hler ist dies beispielsweise ein Nullwert.
Betrachten Sie ein Beispiel - eine allgemeine Einkaufsliste. Einer der Benutzer fĂŒgt Mehl hinzu und der zweite fĂŒhrt eine Kaufabwicklung durch (dies fĂŒhrt zu einem Aufruf des Löschvorgangs fĂŒr alle Elemente). Infolgedessen bleibt eine Einheit Mehl auf der Liste, was logisch erscheint.
Karte entfernen-gewinntDie Operation rmv hat Vorrang.
Beispiel: In einem Online-Spiel hat ein Alice-Spieler 10 MĂŒnzen und einen Hammer. Dann treten gleichzeitig zwei Ereignisse auf: Auf Replik A hat sie einen Nagel hergestellt, und auf Replik B wird ihr Charakter durch Entfernen aller Objekte gelöscht:
Beachten Sie, dass bei Verwendung von "Entfernen als rekursiv" möglicherweise ein Nagel verbleibt. Dies ist nicht der richtige Zustand, wenn das Zeichen entfernt wird.
Update gewinnt KarteAktualisierungen haben Vorrang oder brechen frĂŒhere VorgĂ€nge ab, um gleichzeitig rmv zu löschen.
Beispiel: In einem Online-Spiel wird der Alice-Charakter auf Replik B aufgrund von InaktivitĂ€t gelöscht, aber gleichzeitig wird AktivitĂ€t auf Replik A ausgefĂŒhrt. Offensichtlich muss der Löschvorgang abgebrochen werden.
Bei der Arbeit mit einer solchen Implementierung gibt es einen interessanten Effekt: Angenommen, wir haben zwei Replikate, A und B, und sie speichern die Menge mit einem SchlĂŒssel k. Wenn dann A den Wert des SchlĂŒssels k löscht und B alle Elemente der Menge löscht, hinterlassen die Replikate am Ende eine leere Menge mit dem SchlĂŒssel k.
Beachten Sie, dass eine naive Implementierung nicht ordnungsgemÀà funktioniert. Sie können nicht einfach alle vorherigen LöschvorgĂ€nge rĂŒckgĂ€ngig machen. Im folgenden Beispiel wĂ€re bei diesem Ansatz der Endzustand der Anfangszustand, was falsch ist:
Liste
Das Problem bei diesem Typ besteht darin, dass Elementindizes auf verschiedenen Replikaten nach lokalen EinfĂŒge- / LöschvorgĂ€ngen unterschiedlich sind. Um dieses Problem zu lösen, wird der Operational Transformation-Ansatz angewendet. Bei der Anwendung der erhaltenen Ănderung sollte der Index des Elements im ursprĂŒnglichen Replikat berĂŒcksichtigt werden.
7. Riak
Betrachten Sie als Beispiel CRDT in Riak:
- ZĂ€hler: PN-ZĂ€hler
- Set: OR-Set
- Karte: Update gewinnt Karte der CRDTs
- (Boolesches) Flag: OR-Set wo maximal 1 Element
- Register: Paare (Wert, Zeitstempel)
8. Wer verwendet CRDT?
Der
Wiki- Bereich enthÀlt gute Beispiele.
9. Referenzen