F: Ein Serviceserver, eine Datenbank, Operation: Abfrage des aktuellen Kontostands des Nutzers, abziehen Sie 3 % des aktuellen Guthabens als Bearbeitungsgebühr
Synchronisiert Schleuse DB-Sperre
F: Zwei Serviceserver, eine Datenbank, Betrieb: Abfrage des aktuellen Kontostands des Nutzers, abziehen Sie 3 % des aktuellen Saldos als Bearbeitungsgebühr Verteilte Sperren
Welche Art von verteilter Sperre brauchen wir? Es kann sicherstellen, dass in einem verteilten Anwendungscluster dieselbe Methode nur von einem Thread gleichzeitig auf einer Maschine ausgeführt werden kann.
Wenn diese Sperre eine Reentrant-Sperre ist (vermeiden Sie Deadlocks)
Dieses Schloss eignet sich am besten als Sperrschloss (überlege, ob du es entsprechend deinem Geschäftsbedarf verwenden möchtest).
Dieses Schloss ist am besten, wenn es ein faires Schloss ist (überlege, ob du dieses willst, je nach geschäftlichen Bedürfnissen).
Es gibt sehr verbreitete Erfassungs- und Freigabe-Lock-Funktionen
Die Leistung der Erfassungs- und Freigabe-Sperren ist besser
1. Verteilte Sperren basierend auf Datenbanken
Verteilte Sperren basierend auf tabellenbasierten Implementierungen
Wenn wir eine Methode sperren wollen, führen wir folgendes SQL aus: in methodLock(method_name,desc)-Werte einfügen ('method_name','desc')
Da wir eine Eindeutigkeitsbedingung auf die method_name gesetzt haben, stellt die Datenbank sicher, dass nur eine Operation erfolgreich ist, wenn mehrere Anfragen gleichzeitig an die Datenbank gesendet werden; dann können wir davon ausgehen, dass der Thread, der die Methodensperre erfolgreich erhalten hat, den Inhalt des Methodenkörpers ausführen kann.
Wenn die Methode ausgeführt wird, musst du, wenn du das Lock aufheben möchtest, folgendes SQL ausführen: delete from methodLock, wo method_name ='method_name'
Diese einfache Implementierung oben hat folgende Probleme:
- Diese Sperre hängt von der Verfügbarkeit der Datenbank ab, die ein einzelner Punkt ist und dazu führt, dass das Geschäftssystem nicht mehr verfügbar ist, sobald die Datenbank aufgelegt wird.
- Dieses Schloss hat keine Ablaufzeit, und sobald die Entsperrungsoperation fehlschlägt, bleibt der Sperreintrag in der Datenbank und andere Threads können das Schloss nicht mehr abrufen.
- Diese Sperre kann nur nicht blockierend sein, da die Einfügungsoperation der Daten direkt einen Fehler meldet, sobald die Einfügung fehlschlägt. Threads, die keine Sperren erfassen, gelangen nicht in die Warteschlange und müssen die Lock-Akquisition-Operation erneut auslösen, um die Sperre erneut zu erhalten.
- Das Schloss ist nicht reentrant, und dasselbe Gewinde kann das Schloss erst wieder erhalten, wenn es gelöst wird. Weil die Daten in den Daten bereits existieren.
- Dieses Schloss ist ein unfaires Schloss, und alle Gewinde, die auf das Schloss warten, konkurrieren durch Glück um das Schloss.
Natürlich können wir die oben genannten Probleme auch auf andere Weise lösen.
- Ist die Datenbank ein einzelner Punkt? Baut man zwei Datenbanken, werden die Daten in beide Richtungen synchronisiert. Sobald du aufgelegt hast, wechsle schnell zur Backup-Bibliothek.
- Keine Verfallszeit? Führe einfach eine geplante Aufgabe durch, um die Timeout-Daten in der Datenbank regelmäßig zu bereinigen.
- Nicht blockierend? Mach eine While-Schleife, bis der Insert erfolgreich ist und dann den Erfolg zurückgibt.
- Nicht-reentrant? Füge ein Feld in die Datenbanktabelle hinzu, um die Host-Informationen und die Thread-Informationen der Maschine aufzuzeichnen, die aktuell die Sperre erhält, und beim nächsten Mal, wenn du die Sperre erhältst, frage zuerst die Datenbank ab. Wenn die Host-Informationen und Thread-Informationen des aktuellen Rechners in der Datenbank zu finden sind, kannst du das Schloss direkt ihm zuweisen.
- Unfair? Erstelle eine weitere Zwischentabelle, um alle auf das Schloss wartenden Threads aufzuzeichnen und sortiere sie nach der Erstellungszeit, wobei nur der zuerst erstellte Thread die Sperre erhalten darf
Verteilte Schlösser basierend auf exklusiven Schlössern
Neben dem Hinzufügen und Löschen von Datensätzen in der Datentabelle können auch verteilte Sperren mit Hilfe der mit den Daten gelieferten Sperren implementiert werden.
Wir verwenden auch die gerade erstellte Datenbanktabelle. Verteilte Sperren können durch exklusive Sperren in Datenbanken implementiert werden. Die auf MySQL basierende InnoDB-Engine kann folgende Methoden zur Implementierung von Lock-up-Operationen verwenden:
Wenn Sie nach der Abfrageanweisung für Update hinzufügen, fügt die Datenbank während des Abfrageprozesses eine exklusive Sperre zur Datenbanktabelle hinzu. Wenn eine exklusive Sperre zu einem Datensatz hinzugefügt wird, können andere Threads keine exklusive Sperre mehr zum Datensatz auf dieser Zeile hinzufügen.
Wir können uns vorstellen, dass der Thread, der das exklusive Schloss erhält, auch das verteilte Schloss erhalten kann, und wenn das Schloss erreicht ist, kann die Geschäftslogik der Methode ausgeführt und dann durch die folgenden Methoden freigeschaltet werden:
Public Void unlock(){ connection.commit(); }
via connection.commit(); Operation zur Öffnung des Schlosses.
Diese Methode kann die oben genannten Probleme bezüglich der Unfähigkeit, das Schloss zu lösen und zu blockieren, effektiv lösen.
Schlösser blockieren? Die For Update-Anweisung kehrt sofort nach erfolgreicher Ausführung zurück und bleibt blockiert, bis sie erfolgreich ist.
Dienst nach der Sperre ausgefallen, kann nicht freigegeben werden? Auf diese Weise löst die Datenbank die Sperre nach dem Ausfall des Dienstes von selbst.
Allerdings löst es das Problem von Datenbank-Einzelpunkt, Reentranz und Fair Locking immer noch nicht direkt.
Um zusammenzufassen, wie die Datenbank zur Implementierung verteilter Sperren verwendet werden kann, die beide auf einer Tabelle in der Datenbank basieren: Erstens wird festgestellt, ob es eine Sperre gibt, anhand der Existenz von Datensätzen in der Tabelle, und zum anderen werden verteilte Sperren über das exklusive Schloss der Datenbank implementiert.
Vorteile der verteilten Sperrung in Datenbanken
Direkt mit Hilfe der Datenbank ist es leicht verständlich.
Nachteile der Implementierung verteilter Sperren in Datenbanken
Es wird verschiedene Probleme geben, und die gesamte Lösung wird im Prozess der Lösung immer komplexer.
Der Betrieb der Datenbank erfordert einen gewissen Overhead, und Leistungsprobleme müssen berücksichtigt werden.
2. Verteilte Sperren basierend auf Cache
Im Vergleich zur datenbankbasierten verteilten Sperrlösung wird die cachebasierte Implementierung in Bezug auf die Leistung besser performen.
Derzeit gibt es viele ausgereifte Caching-Produkte, darunter Redis, memchad usw. Hier nehmen wir Redis als Beispiel, um das Schema zu analysieren, mit dem Cache verteilte Sperren implementiert wird.
Es gibt viele verwandte Artikel im Internet über die Implementierung verteilter Sperren auf Basis von Redis, und die Hauptimplementierungsmethode ist die Verwendung der Jedis.setNX-Methode.
Es gibt auch mehrere Probleme mit der obigen Implementierung:
1. Einpunktsproblem.
2. Diese Sperre hat keine Ablaufzeit; sobald die Entsperrungsoperation fehlschlägt, bleibt der Sperrdatensatz ständig in Redis, und andere Threads können die Sperre nicht mehr erhalten.
3. Diese Sperre kann nur nicht blockierend sein und kehrt unabhängig von Erfolg oder Misserfolg direkt zurück.
4. Dieses Schloss ist nicht rückgängig; nachdem ein Thread das Schloss erhalten hat, kann es das Schloss nicht erneut erhalten, bevor es das Schloss freigibt, da der verwendete Schlüssel bereits in Redis existiert. setNX-Operationen können nicht mehr ausgeführt werden.
5. Diese Sperre ist unfair, alle wartenden Threads starten setNX-Operationen gleichzeitig, und glückliche Threads können die Sperre bekommen.
Natürlich gibt es auch Wege, das zu lösen.
- Heutzutage unterstützen gängige Caching-Dienste die Cluster-Bereitstellung, um Einzelpunktprobleme durch Clustering zu lösen.
- Keine Verfallszeit? Die setExpire-Methode von redis unterstützt die eingehende Verfallszeit, und die Daten werden automatisch gelöscht, sobald diese Zeit erreicht ist.
- Nicht blockierend? während sie wiederholt hingerichtet wurde.
- Ist es nicht möglich, wieder einzutreten? Nachdem ein Thread das Schloss erworben hat, speichere die aktuellen Host- und Threadinformationen und prüfe, ob du der Eigentümer des aktuellen Locks bist, bevor du es das nächste Mal abholst.
- Unfair? Setze alle wartenden Threads in eine Warteschlange, bevor ein Thread eine Sperre erhält, und erwirbe dann die Sperre auf der First-in-First-out-Basis.
Die Synchronisationspolitik des Redis-Clusters benötigt Zeit, und es ist möglich, dass Thread A nach erfolgreicher Einstellung von NX eine Sperre erhält, aber dieser Wert wurde noch nicht auf den Server aktualisiert, auf dem Thread B setNX ausführt, was zu Nebenläufigkeitsproblemen führen kann.
Salvatore Sanfilippo, der Autor von redis, schlug den Redlock-Algorithmus vor, der eine verteilte Sperrverwaltung (DLM) implementiert, die sicherer und zuverlässiger ist als ein einzelner Knoten.
Der Redlock-Algorithmus geht davon aus, dass es N Redis-Knoten gibt, die unabhängig voneinander sind, im Allgemeinen auf N=5 gesetzt, und diese N Knoten laufen auf verschiedenen Rechnern, um die physikalische Unabhängigkeit zu wahren.
Die Schritte des Algorithmus sind wie folgt:
1. Der Client erhält die aktuelle Zeit in Millisekunden. 2. Der Client versucht, das Schloss von N Knoten zu erhalten (jeder Knoten erhält das Schloss auf die gleiche Weise wie das zuvor erwähnte Cache-Schloss), und N Knoten erhalten das Schloss mit demselben Schlüssel und Wert. Der Client muss das Interface-Zugriffs-Timeout einstellen, und das Interface-Timeout muss deutlich kürzer sein als das Lock-Timeout, zum Beispiel beträgt die automatische Lock-Release-Zeit 10 Sekunden, dann wird das Interface-Timeout auf etwa 5-50 ms gesetzt. Dadurch kannst du beim Zugriff auf einen Redis-Knoten nach dessen Ausfall so schnell wie möglich auslaufen und verringerst die normale Nutzung des Schlosses. 3. Der Client berechnet, wie viel Zeit es braucht, um die Sperre zu erhalten, indem er die in Schritt 1 erreichte Zeit mit der aktuellen Zeit abzieht; nur wenn der Client mehr als 3 Knoten des Schlosses erhält und die Zeit zum Erlangen des Schlosses kürzer ist als die Zeitabbruchzeit der Sperre, erhält der Client das verteilte Schloss. 4. Die Zeit, die der Client benötigt, um das Schloss zu erhalten, ist die festgelegte Lock-Timeout-Zeit abzüglich der in Schritt 3 berechneten Zeit für die Erfassung des Locks. 5. Wenn der Client die Sperre nicht erhält, löscht er alle Sperren nacheinander. Mit dem Redlock-Algorithmus kann garantiert werden, dass der Distributed Lock Service weiterhin funktioniert, wenn bis zu zwei Knoten aufgehängt werden, was die Verfügbarkeit im Vergleich zum vorherigen Datenbank- und Cache-Lock erheblich verbessert.
Ein verteilter Experte verfasste jedoch einen Artikel mit dem Titel "How to do distributed locking", in dem er die Richtigkeit von Redlock infrage stellte.
Der Experte erwähnte, dass es zwei Aspekte gibt, die bei verteilten Sperren zu berücksichtigen sind: Leistung und Korrektheit.
Wenn Sie ein Hochleistungs-Distributed Lock verwenden und die Korrektheit nicht erforderlich ist, reicht die Verwendung eines Cache-Locks aus.
Wenn Sie ein hochzuverlässiges verteiltes Schloss verwenden, müssen Sie strenge Zuverlässigkeitsfragen berücksichtigen. Redlock hingegen erfüllt die Korrektheit nicht. Warum nicht? Experten listen mehrere Aspekte auf.
Heutzutage verwenden viele Programmiersprachen virtuelle Maschinen mit GC-Funktionen; bei Full GC hört das Programm auf, GC zu verarbeiten, manchmal dauert Full GC lange, und selbst das Programm hat ein paar Minuten Verzögerung; der Artikel listet das Beispiel HBase auf, HBase manchmal GC für ein paar Minuten, was dazu führt, dass der Leasing ausläuft. Zum Beispiel erhält Client 1 in der untenstehenden Abbildung eine Sperre und steht kurz davor, eine gemeinsame Ressource zu verarbeiten, und wenn sie eine gemeinsame Ressource verarbeiten soll, erfolgt die vollständige GC, bis die Sperre abläuft. So bekommt Client 2 die Sperre wieder und beginnt mit der gemeinsamen Ressource. Wenn Client 2 verarbeitet wird, schließt Client 1 das vollständige GC ab und beginnt, gemeinsam genutzte Ressourcen zu verarbeiten, sodass beide Clients gemeinsam genutzte Ressourcen verarbeiten.
Experten gaben eine Lösung, wie in der untenstehenden Abbildung gezeigt: MVCC bringt ein Token zum Lock, Token ist das Konzept der Version, jedes Mal, wenn die Operation Lock abgeschlossen ist, wird das Token hinzugefügt: 1, bring den Token bei der Verarbeitung gemeinsamer Ressourcen, nur die angegebene Version des Tokens kann die gemeinsame Ressource verarbeiten.
Dann sagte der Experte auch, dass der Algorithmus auf lokale Zeit basiert und Redis auf die getTimeOfDay-Methode setzt, um die Zeit statt der monotonen Uhr zu erhalten, wenn der Schlüsselablauf verwendet wird, was ebenfalls zu Zeitungenauigkeiten führt. Zum Beispiel haben in einem Szenario zwei Client 1 und Client 2 5 Redis-Knoten (A, B, C, D und E).
1. Client 1 erhält erfolgreich die Sperre von A, B und C und erhält den Lock-Netzwerk-Timeout von D und E. 2. Die Uhr von Knoten C ist ungenau, was zum Lock-Timeout führt. 3. Client 2 erhält erfolgreich die Sperre von C, D und E und erhält den Lock-Netzwerk-Timeout von A und B. 4. Auf diese Weise erhalten sowohl Client 1 als auch Client 2 eine Sperre. Um die beiden Punkte zusammenzufassen, die Experten über Redlocks Nichtverfügbarkeit sagen:
1. GC und andere Szenarien können jederzeit auftreten, wodurch der Client eine Sperre erhält, und die Verarbeitungszeitpause bewirkt, dass ein anderer Client die Sperre erhält. Experten fanden auch eine Lösung für die Verwendung selbstinkrementierender Tokens. 2. Der Algorithmus basiert auf der lokalen Zeit, und die Uhr ist ungenau, sodass zwei Clients gleichzeitig Sperren erhalten. Daher kommen Experten zu dem Fazit, dass Redlock nur im begrenzten Netzwerkverzögerungsbereich, begrenzten Programmunterbrechungen und begrenzten Taktfehlerbereich normal funktionieren kann, aber die Grenzen dieser drei Szenarien können nicht bestätigt werden, weshalb Experten nicht empfehlen, Redlock zu verwenden. Für Szenarien mit hohen Korrektheitsanforderungen empfehlen Experten Zookeeper, das später diskutiert wird, Zookeeper als verteiltes Schloss zu verwenden.
Antwort des Autors von Redis
Der Redis-Autor reagierte, indem er einen Blog schrieb, nachdem er den Artikel des Experten gesehen hatte. Der Autor dankte dem Experten höflich und äußerte dann seine Ablehnung der Meinung des Experten.
Ich habe hier um eine Analyse der ursprünglichen Redlock-Spezifikation gebeten:http://redis.io/topics/distlock.Also danke, Martin. Allerdings stimme ich der Analyse nicht zu.
Die Diskussion des REDIS-Autors über die Verwendung von Token zur Lösung des Lock-Timeout-Problems lässt sich in den folgenden fünf Punkten zusammenfassen:
Punkt 1: Die Verwendung von verteilten Sperren besteht im Allgemeinen darin, dass es keine andere Möglichkeit gibt, gemeinsame Ressourcen zu kontrollieren, Experten verwenden Tokens, um die Verarbeitung der gemeinsamen Ressourcen sicherzustellen, und dann sind verteilte Sperren nicht nötig. Punkt 2: Bei der Token-Generierung benötigt der Dienst, der Tokens erzeugt, weiterhin verteilte Sperren, um die Zuverlässigkeit der von verschiedenen Clients erhaltenen Token zu gewährleisten. Punkt 3: Für die Art und Weise, wie Experten selbstinkrementierende Token sagen, hält der Redis-Autor dies für völlig unnötig, jeder Client kann ein eindeutiges UUID als Token generieren und die geteilte Ressource in einen Zustand setzen, den nur der Client mit UUID verarbeiten kann, sodass andere Clients die geteilte Ressource erst verarbeiten können, wenn der Client, der das Schloss erhält, das Schloss freigibt. Wie in der obigen Abbildung gezeigt, kann ein anderer Client, wenn der Client von Token 34 während des Schreibvorgangs GC sendet und die Sperre zum Timeout führt, die Sperre von Token 35 erhalten und erneut mit dem Schreiben beginnen, was zu einem Sperrkonflikt führt. Daher kann die Reihenfolge der Token nicht mit gemeinsamen Ressourcen kombiniert werden. Punkt 5: Der Redis-Autor glaubt, dass in den meisten Szenarien verteilte Sperren verwendet werden, um Aktualisierungsprobleme in nicht-transaktionalen Szenarien zu lösen. Der Autor sollte darauf hinweisen, dass es Szenarien gibt, in denen es schwierig ist, Token zu kombinieren, um gemeinsame Ressourcen zu handhaben, sodass man sich auf Sperren verlassen muss, um Ressourcen zu sperren und zu verarbeiten. Ein weiteres Uhrenproblem, über das Experten sprechen, gibt auch eine Erklärung der Redis-Autoren. Wenn die Zeit, die zum Erlangen des Schlosses benötigt wird, zu lang ist und die Standard-Timeout-Zeit des Schlosses übersteigt, kann der Client das Schloss zu diesem Zeitpunkt nicht erhalten, und Experten werden keine Beispiele vorschlagen.
Persönliche Gefühle
Das erste Problem, das ich zusammenfasse, ist, dass nach dem Erhalt eines verteilten Locks durch einen Client die Sperre nach einem Timeout während der Verarbeitung des Clients freigegeben werden kann. Früher, wenn man über die durch die Datenbanksperre gesetzte 2-Minuten-Sperre gesprochen wurde, kann das andere Handelszentrum, wenn eine Aufgabe länger als 2 Minuten eine Ordersperre besetzt, diese Ordersperre erhalten, sodass beide Handelszentren dieselbe Bestellung gleichzeitig verarbeiten können. Unter normalen Umständen wird die Aufgabe innerhalb von Sekunden verarbeitet, aber manchmal ist die durch das Beitreten einer RPC-Anfrage gesetzte Timeout zu lang, und es gibt mehrere solcher Timeout-Anfragen in einer Aufgabe, sodass die automatische Entsperrungszeit wahrscheinlich überschritten wird. Wenn wir in Java schreiben, kann es in der Mitte Full GC geben, sodass der Client nach dem Lock-Timeout nach dem Lock-Timeout das Schloss nicht mehr wahrnehmen kann, was eine sehr ernste Sache ist. Ich glaube nicht, dass das ein Problem mit dem Schloss selbst ist, solange jedes oben erwähnte verteilte Schloss die Eigenschaften einer Timeout-Freigabe hat, wird ein solches Problem auftreten. Wenn Sie die Lock-Timeout-Funktion verwenden, muss der Client die Lock-Timeout setzen und entsprechend handeln, anstatt die gemeinsame Ressource weiter zu verarbeiten. Der Redlock-Algorithmus gibt die Sperrzeit zurück, die der Client nach dem Erwerb der Sperre einnehmen kann, und der Client muss diese Zeit verarbeiten, um die Aufgabe danach zu stoppen.
Das zweite Problem ist, dass verteilte Experten Redlock nicht verstehen. Ein zentrales Merkmal von Redlock ist, dass die Zeit, das Schloss zu erlangen, die Gesamtzeit entspricht, in der das Schloss standardmäßig auf Timeout abzüglich der Zeit, die benötigt wird, um das Schloss zu erlangen, sodass die Verarbeitungszeit des Clients relativ lang ist, unabhängig von der lokalen Zeit.
Aus dieser Sicht kann die Korrektheit von Redlock gut garantiert werden. Sorgfältige Analyse von Redlock im Vergleich zu Redis eines Knotens ist das Hauptmerkmal von Redlock eine höhere Zuverlässigkeit, was in manchen Szenarien ein wichtiges Merkmal ist. Aber ich denke, Redlock hat zu viel Geld ausgegeben, um Zuverlässigkeit zu erreichen.
- Erstens müssen 5 Knoten eingesetzt werden, um Redlock zuverlässiger zu machen.
- Dann musst du 5 Knoten anfordern, um die Sperre zu erhalten, und mit der Future-Methode kannst du zuerst gleichzeitig 5 Knoten anfordern und dann das Antwortergebnis zusammenstellen, was die Antwortzeit verkürzen kann, aber trotzdem mehr Zeit in Anspruch nimmt als ein Redis-Lock mit einem einzelnen Knoten.
- Da dann mehr als 3 der 5 Knoten erreicht werden müssen, kann es zu einem Lock-Konflikt kommen, das heißt, jeder hat 1-2 Locks, und dadurch kann niemand die Locke bekommen. Dieses Problem übernimmt der Redis-Autor das Wesen des Raft-Algorithmus: Durch die Kollision zum zufälligen Zeitpunkt kann die Konfliktzeit stark reduziert werden, aber dieses Problem lässt sich nicht gut vermeiden, besonders wenn die Locke zum ersten Mal erlangt wird, sodass die Zeitkosten für die Erfassung der Locke steigen.
- Wenn 2 der 5 Knoten ausfallen, wird die Verfügbarkeit der Sperre stark reduziert; zunächst muss man warten, bis die Ergebnisse dieser beiden ausgeschalteten Knoten ablaufen, bevor man zurückkehrt, und es gibt nur 3 Knoten, und der Client muss die Sperren aller 3 Knoten erhalten, um die Sperre zu erhalten, was ebenfalls schwieriger ist.
- Wenn es eine Netzwerkpartition gibt, kann es sein, dass der Client das Schloss nie erhalten kann.
Nach der Analyse so vieler Gründe denke ich, dass der wichtigste Punkt von Redlocks Problem ist, dass Redlock von den Clients verlangt, die Konsistenz der Schreibvorgänge sicherzustellen, und die fünf Backend-Knoten sind völlig unabhängig, sodass alle Clients diese fünf Knoten bedienen müssen. Wenn es einen Leader unter 5 Knoten gibt, kann der Client die Daten des Leaders synchronisieren, solange der Client die Sperre vom Leader erhält, sodass es keine Probleme wie Partitionierung, Timeouts und Konflikte gibt. Daher denke ich, dass der Einsatz eines verteilten Koordinationsdienstes mit starker Konsistenz das Problem besser lösen kann, um die Korrektheit verteilter Sperren zu gewährleisten.
Die Frage stellt sich erneut: Wie lange sollte ich die Ablaufzeit einstellen? Wie man die Invalidierungszeit einstellt, ist zu kurz, und die Sperre wird automatisch aufgehoben, bevor die Methode ausgeführt wird, sodass es zu Nebenfallproblemen kommt. Wenn es zu lange dauert, müssen andere Threads, die das Schloss bekommen, möglicherweise lange warten.
Dieses Problem besteht auch bei der Verwendung von Datenbanken zur Implementierung verteilter Sperren.
Der aktuelle gängige Ansatz für dieses Problem besteht darin, für jede erreichte Sperre eine kurze Auszeitszeit einzusetzen und einen Thread zu starten, um die Sperrzeitzeit jedes Mal zu aktualisieren, wenn sie kurz vor dem Auszeitpunkt steht. Beende diesen Thread gleichzeitig mit dem Öffnen des Schlosses. Zum Beispiel verwendet Redisson, die offizielle verteilte Sperrkomponente von Redis, diese Lösung.
Vorteile der Verwendung von Caching zur Implementierung verteilter Sperren Gute Leistung.
Nachteile der Verwendung von Caching zur Implementierung verteilter Sperren Die Umsetzung ist zu verantwortungsbewusst, es gibt zu viele Faktoren zu berücksichtigen.
Verteilte Schlösser basierend auf der Zookeeper-Implementierung
Verteilte Sperren basierend auf temporären Zookeeper-Knoten.
Die Grundidee ist, dass beim Sperren jeder Client eine Methode ein eindeutiger augenblicklicher geordneter Knoten im Verzeichnis des angegebenen Knotens erzeugt wird, der der Methode auf Zookeeper entspricht. Der Weg, um zu bestimmen, ob man eine Sperre bekommt, ist einfach: Man muss nur die mit der kleinsten Seriennummer im geordneten Knoten bestimmen. Wenn die Sperre freigegeben wird, löschen Sie einfach den Instantan-Knoten. Gleichzeitig kann es das Problem von Deadlocks vermeiden, die durch Service-Downtime entstehen und nicht aufgehoben werden können.
Mal sehen, ob Zookeeper die zuvor genannten Probleme lösen kann.
- Das Schloss lässt sich nicht öffnen? Die Verwendung von Zookeeper kann das Problem der Sperrungen effektiv lösen, denn beim Erstellen eines Schlosses erstellt der Client einen temporären Knoten in ZK, und sobald der Client das Schloss erhält und es plötzlich aufhängt (die Sitzungsverbindung ist unterbrochen), wird der temporäre Knoten automatisch gelöscht. Andere Kunden können das Schloss wieder bekommen.
- Nicht blockierende Schlösser? Sobald sich der Knoten ändert, benachrichtigt Zookeeper den Client, und der Client kann überprüfen, ob der erstellte Knoten die kleinste Ordinalzahl aller Knoten ist.
- Kannst du nicht wieder rein? Wenn der Client einen Knoten erstellt, schreibt er direkt die Host- und Threadinformationen des aktuellen Clients an den Knoten, und beim nächsten Mal, wenn du die Sperre holen möchtest, kannst du sie mit den Daten im aktuell kleinsten Knoten vergleichen. Wenn die Information mit deiner eigenen identisch ist, kannst du das Schloss direkt erhalten und, falls es anders ist, einen temporären sequentiellen Knoten erstellen, der an der Warteschlange teilnimmt.
Die Frage stellt sich erneut: Wir wissen, dass Zookeeper in Clustern eingesetzt werden muss, wird es Probleme mit der Datensynchronisation wie bei Redis-Clustern geben?
Zookeeper ist eine verteilte Komponente, die schwache Konsistenz garantiert, also schließlich Konsistenz.
Zookeeper verwendet ein Datensynchronisationsprotokoll namens Quorum Based Protocol. Wenn es N Zookeeper-Server im Zookeeper-Cluster gibt (N ist meist ungerade, 3 erreichen Datenzuverlässigkeit und bieten eine hohe Lese- und Schreibleistung, und 5 haben das beste Gleichgewicht zwischen Datenzuverlässigkeit und Lese- und Schreibleistung), wird zunächst eine Schreiboperation des Benutzers auf N/2 + 1 Server synchronisiert und dann an den Benutzer zurückgegeben, wodurch der Benutzer erfolgreich schreiben muss. Das Datensynchronisationsprotokoll, das auf dem Quorum-basierten Protokoll basiert, bestimmt die Konsistenz der Stärke, die Zookeeper unterstützen kann.
In einer verteilten Umgebung ist eine Datenspeicherung mit hoher Konsistenz praktisch nicht vorhanden, und es ist erforderlich, dass alle Knoten synchron aktualisiert werden, wenn die Daten eines Knotens aktualisiert werden. Diese Synchronisationsstrategie erscheint in der Master-Slave-Datenbank für synchrone Replikation. Diese Synchronisationsstrategie hat jedoch zu große Auswirkungen auf die Schreibleistung und ist in der Praxis selten zu sehen. Da Zookeeper N/2+1 Knoten synchron schreibt und N/2 Knoten nicht synchron aktualisiert werden, ist Zookeeper nicht stark konsistent.
Die Datenaktualisierungsoperation des Benutzers garantiert nicht, dass nachfolgende Lesungen den aktualisierten Wert lesen, zeigt aber schließlich Konsistenz. Konsistenz zu opfern bedeutet nicht, die Konsistenz der Daten vollständig zu ignorieren, sonst sind die Daten chaotisch, sodass egal wie hoch die Systemverfügbarkeit ist, egal wie gut die Verteilung ist, sie keinen Wert haben. Konsistenz zu opfern bedeutet einfach, dass starke Konsistenz in relationalen Datenbanken nicht mehr erforderlich ist, solange das System letztendliche Konsistenz erreichen kann.
Eine Frage mit nur einem Punkt? Die Nutzung von Zookeeper kann ein Einzelpunktproblem effektiv lösen; ZK wird in Clustern eingesetzt, solange mehr als die Hälfte der Maschinen im Cluster überlebt, kann der Dienst der Außenwelt bereitgestellt werden.
Probleme mit der Fairness? Die Verwendung von Zookeeper kann das Problem der fairen Sperren lösen: Die vom Client in ZK erstellten temporären Knoten sind geordnet, und jedes Mal, wenn die Sperre freigegeben wird, kann ZK den kleinsten Knoten benachrichtigen, um die Sperre zu erhalten, um die Fairness zu gewährleisten.
Die Frage stellt sich erneut: Wir wissen, dass Zookeeper in Clustern eingesetzt werden muss, wird es Probleme mit der Datensynchronisation wie bei Redis-Clustern geben?
Zookeeper ist eine verteilte Komponente, die schwache Konsistenz garantiert, also schließlich Konsistenz.
Zookeeper verwendet ein Datensynchronisationsprotokoll namens Quorum Based Protocol. Wenn es N Zookeeper-Server im Zookeeper-Cluster gibt (N ist meist ungerade, 3 erreichen Datenzuverlässigkeit und bieten eine hohe Lese- und Schreibleistung, und 5 haben das beste Gleichgewicht zwischen Datenzuverlässigkeit und Lese- und Schreibleistung), wird zunächst eine Schreiboperation des Benutzers auf N/2 + 1 Server synchronisiert und dann an den Benutzer zurückgegeben, wodurch der Benutzer erfolgreich schreiben muss. Das Datensynchronisationsprotokoll, das auf dem Quorum-basierten Protokoll basiert, bestimmt die Konsistenz der Stärke, die Zookeeper unterstützen kann.
In einer verteilten Umgebung ist eine Datenspeicherung mit hoher Konsistenz praktisch nicht vorhanden, und es ist erforderlich, dass alle Knoten synchron aktualisiert werden, wenn die Daten eines Knotens aktualisiert werden. Diese Synchronisationsstrategie erscheint in der Master-Slave-Datenbank für synchrone Replikation. Diese Synchronisationsstrategie hat jedoch zu große Auswirkungen auf die Schreibleistung und ist in der Praxis selten zu sehen. Da Zookeeper N/2+1 Knoten synchron schreibt und N/2 Knoten nicht synchron aktualisiert werden, ist Zookeeper nicht stark konsistent.
Die Datenaktualisierungsoperation des Benutzers garantiert nicht, dass nachfolgende Lesungen den aktualisierten Wert lesen, zeigt aber schließlich Konsistenz. Konsistenz zu opfern bedeutet nicht, die Konsistenz der Daten vollständig zu ignorieren, sonst sind die Daten chaotisch, sodass egal wie hoch die Systemverfügbarkeit ist, egal wie gut die Verteilung ist, sie keinen Wert haben. Konsistenz zu opfern bedeutet einfach, dass starke Konsistenz in relationalen Datenbanken nicht mehr erforderlich ist, solange das System letztendliche Konsistenz erreichen kann.
Ob Zookeeper die kausale Konsistenz erfüllt, hängt davon ab, wie der Client programmiert ist.
Praktiken, die die kausale Konsistenz nicht erfüllen
- Prozess A schreibt ein Datenstück auf Zookeepers /z und gibt erfolgreich zurück
- Prozess A informiert Prozess B, dass A die Daten von /z modifiziert hat
- B liest die Daten von Zookeepers /z aus
- Da der Server des Zookeepers, der mit B verbunden ist, möglicherweise nicht mit den geschriebenen Daten von A aktualisiert wurde, kann B die schriftlichen Daten von A nicht lesen
Praktiken, die kausale Konsistenz erfüllen
- Prozess B hört auf Datenänderungen in /z auf Zookeeper
- Prozess A schreibt ein Datenstück in Zookeepers /z, und bevor es erfolgreich zurückkehrt, muss Zookeeper den auf /z registrierten Zuhörer aufrufen, und der Leader informiert B über die Datenänderung
- Nachdem die Ereignisantwortmethode von Prozess B beantwortet wurde, nimmt sie die geänderten Daten, sodass B definitiv den geänderten Wert erhalten kann
- Kausale Konsistenz bezieht sich hier auf die kausale Konsistenz zwischen Leader und B, das heißt, der Leader informiert die Daten über eine Änderung
Der zweite Ereignis-Listening-Mechanismus ist auch die Methode, die verwendet werden sollte, um Zookeeper richtig zu programmieren, sodass Zookeeper die kausale Konsistenz erfüllen sollte
Daher sollten wir bei der Implementierung verteilter Sperren auf Basis von Zookeeper die Praxis verwenden, kausale Konsistenz zu erfüllen, das heißt, die Threads, die auf die Sperre warten, hören auf die Änderungen im Lock von Zookeeper, und wenn die Sperre freigegeben wird, benachrichtigt Zookeeper den wartenden Thread, der die fair-lock-Bedingungen erfüllt.
Sie können direkt den Drittanbieter-Bibliotheksclient Zookeeper verwenden, der einen Reentrant-Lock-Service kapselt.
Distributed Locks, die mit ZK implementiert werden, scheinen genau zu dem zu entsprechen, was wir von einem verteilten Lock zu Beginn dieses Artikels erwartet haben. Das ist jedoch nicht der Fall, und die von Zookeeper implementierte verteilte Sperre hat tatsächlich einen Nachteil, nämlich dass die Leistung möglicherweise nicht so hoch ist wie die des Caching-Dienstes. Denn jedes Mal, wenn im Prozess der Erstellung und Freigabe eines Locks entstehen, müssen momentane Knoten dynamisch erstellt und zerstört werden, um die Lock-Funktion zu realisieren. Das Erstellen und Löschen von Knoten in ZK kann nur über den Leader-Server erfolgen, und dann werden die Daten mit allen Begleitmaschinen geteilt.
Vorteile der Verwendung von Zookeeper zur Implementierung verteilter Schlösser Effektive Lösung von Einzelpunkt-Problemen, Nicht-Wiedereintrittsproblemen, Nicht-Blockade-Problemen und Sperrfehlern beim Öffnen. Es ist relativ einfach umzusetzen.
Nachteile der Verwendung von Zookeeper zur Implementierung verteilter Sperren Die Leistung ist nicht so gut wie die Verwendung von Cache, um verteilte Sperren zu implementieren. Ein Verständnis der Prinzipien von ZK ist erforderlich.
Vergleich der drei Optionen
Aus der Perspektive der Verständlichkeit (von niedrig nach hoch) Datenbank > Cache > Zookeeper
Aus der Perspektive der Implementierungskomplexität (von niedrig nach hoch) Zookeeper > Cache > Datenbanken
Aus Performance-Sicht (von hoch nach tief) Caching > Zookeeper >= Datenbank
Aus Zuverlässigkeitssicht (von hoch nach niedrig) Zookeeper > Cache > Datenbanken
|