Was soll ich tun, wenn das optimistische Sperren nicht funktioniert?


10

Ich habe folgendes Szenario:

  1. Ein Benutzer stellt eine GET- Anfrage an /projects/1und empfängt einen ETag .
  2. Der Benutzer stellt ab Schritt 1 eine PUT- Anfrage /projects/1mit dem ETag.
  3. Der Benutzer stellt /projects/1ab Schritt 1 eine weitere PUT-Anfrage mit dem ETag.

Normalerweise erhält die zweite PUT-Anforderung eine 412-Antwort, da das ETag jetzt veraltet ist. Die erste PUT-Anforderung hat die Ressource geändert, sodass das ETag nicht mehr übereinstimmt.

Was aber, wenn die beiden PUT-Anforderungen gleichzeitig (oder genau nacheinander) gesendet werden? Die erste PUT-Anforderung hat keine Zeit, die Ressource zu verarbeiten und zu aktualisieren, bevor PUT # 2 eintrifft, was dazu führt, dass PUT # 2 PUT # 1 überschreibt. Der springende Punkt beim optimistischen Sperren ist, dass dies nicht geschieht ...


3
Zerstäuben Sie Ihre Abläufe in Transaktionen auf Unternehmensebene, wie Esben weiter unten erläutert.
Robert Harvey

Was würde passieren, wenn ich meine Operationen mithilfe von Transaktionen atomisieren würde? PUT # 2 würde nicht verarbeitet, bis PUT # 1 vollständig verarbeitet ist?
Maximedupre

7
Pessimist werden?
jpmc26

Nun, dafür ist das Sperren da.
Fattie

Richtig, natürlich sollte Put # 2 nicht verarbeitet werden - sie sollen eindeutig sein.
Fattie

Antworten:


21

Der ETag-Mechanismus gibt nur das Kommunikationsprotokoll für optimistisches Sperren an. Es liegt in der Verantwortung des Anwendungsdienstes, den Mechanismus zum Erkennen gleichzeitiger Aktualisierungen zu implementieren, um die optimistische Sperre zu erzwingen.

In einer typischen Anwendung, die eine Datenbank verwendet, öffnen Sie normalerweise eine Transaktion, wenn Sie eine PUT-Anforderung verarbeiten. Normalerweise lesen Sie den vorhandenen Status der Datenbank in dieser Transaktion (um eine Lesesperre zu erhalten), überprüfen Ihre Etag-Gültigkeit und überschreiben die Daten (auf eine Weise, die einen Schreibkonflikt verursacht, wenn eine inkompatible gleichzeitige Transaktion vorliegt). dann begehen. Wenn Sie die Transaktion korrekt eingerichtet haben, sollte eines der Commits fehlschlagen, da beide versuchen, dieselben Daten gleichzeitig zu aktualisieren. Sie können diesen Transaktionsfehler dann verwenden, um entweder 412 zurückzugeben oder die Anforderung erneut zu versuchen, wenn dies für die Anwendung sinnvoll ist.


Der Server implementiert derzeit den Mechanismus zum Erkennen gleichzeitiger Aktualisierungen, indem er Hashes der Ressource vergleicht. Der Server verwendet auch Transaktionen für alle Vorgänge, aber ich erwerbe keine Sperren, was möglicherweise das Problem verursacht. Wie kann es in Ihrem Beispiel jedoch zu einem Fehler bei einem der Commits kommen, wenn die Transaktionen Sperren verwenden? Die zweite Transaktion sollte beim Lesen des Status anstehen, bis die erste Transaktion aufgelöst wird.
Maximedupre

1
@maximedupre: Wenn Sie eine Transaktion verwenden, haben Sie eine Art von Sperren, obwohl dies implizite Sperren sein können (die Sperren werden automatisch erfasst, wenn Sie Felder lesen / aktualisieren, anstatt explizit angefordert zu werden). Der oben beschriebene Mechanismus kann nur mit diesen impliziten Sperren implementiert werden. Als Ihre andere Frage hängt es von der Datenbank ab, die Sie verwenden, aber viele moderne Datenbanken verwenden MVCC (Multi-Version Concurrency Control), damit mehrere Leser und Schreiber an denselben Feldern arbeiten können, ohne sich unnötig zu blockieren.
Lie Ryan

1
Warnung: In vielen DBMS (PostgreSQL, Oracle, SQL Server usw.) ist die Standard-Transaktionsisolationsstufe "read commit", wobei Ihr Ansatz nicht ausreicht, um die Race-Bedingung des OP zu verhindern. In solchen DMBS können Sie das Problem beheben, indem Sie es AND ETag = ...in UPDATEdie WHEREKlausel Ihrer Anweisung aufnehmen und anschließend die Anzahl der aktualisierten Zeilen überprüfen. (Oder durch Verwendung einer strengeren Transaktionsisolationsstufe, aber das empfehle ich nicht wirklich.)
Ruakh

1
@ruakh: Es hängt davon ab, wie Sie Ihre Abfrage schreiben. Ja, die Standardisolationsstufe bietet ein solches Verhalten nicht automatisch für alle Abfragen, aber es ist häufig möglich, Ihre Transaktion so zu strukturieren, dass eine optimistische Sperrung implementiert wird. In den meisten Fällen, wenn die Transaktionskonsistenz in der Anwendung wichtig ist, würde ich das wiederholbare Lesen ohnehin als Standardisolationsstufe empfehlen. In Datenbanken, die MVCC verwenden, ist der Aufwand für wiederholbares Lesen relativ gering und vereinfacht die Anwendung erheblich.
Lie Ryan

1
@ruakh: Der Hauptnachteil des wiederholbaren Lesens besteht darin, dass Sie darauf vorbereitet sein müssen, es erneut zu versuchen oder fehlzuschlagen, wenn gleichzeitig eine Transaktion stattfindet. Dies ist normalerweise ein Problem, aber Anwendungen, die optimistisches Sperren als Parallelitätsstrategie bereitstellen, erfordern diese Behandlung ohnehin bereits, sodass wiederholbare Lesefehler natürlich optimistischen Sperrfehlern zugeordnet werden und dies keine neuen Nachteile mit sich bringt.
Lie Ryan

13

Sie müssen das folgende Paar atomar ausführen:

  • Überprüfung des Tags auf Gültigkeit (dh auf dem neuesten Stand)
  • Aktualisieren der Ressource (einschließlich Aktualisieren des Tags)

Andere nennen dies eine Transaktion - aber im Grunde verhindert die atomare Ausführung dieser beiden Operationen, dass die eine die andere versehentlich überschreibt. Ohne dies haben Sie eine Rennbedingung, wie Sie bemerken.

Dies wird immer noch als optimistisches Sperren angesehen, wenn Sie das Gesamtbild betrachten: Die Ressource selbst wird nicht durch das anfängliche Lesen (GET) eines Benutzers oder eines Benutzers gesperrt, der die Daten betrachtet, unabhängig davon, ob sie aktualisiert werden sollen oder nicht.

Ein gewisses atomares Verhalten ist erforderlich, dies geschieht jedoch innerhalb einer einzelnen Anforderung (PUT), anstatt zu versuchen, eine Sperre für mehrere Netzwerkinteraktionen aufrechtzuerhalten. Dies ist eine optimistische Sperrung: Das Objekt ist vom GET nicht gesperrt, kann jedoch vom PUT sicher aktualisiert werden.

Es gibt auch viele Möglichkeiten, um eine atomare Ausführung dieser beiden Operationen zu erreichen. Das Sperren der Ressource ist nicht die einzige Option. Beispielsweise kann eine einfache Thread- oder Objektsperre ausreichen und hängt von der Architektur und dem Ausführungskontext Ihrer Anwendung ab.


4
+1 für die Feststellung, dass es darauf ankommt, atomar zu sein. Abhängig von der zu aktualisierenden zugrunde liegenden Ressource kann dies ohne Transaktionen oder Sperren erfolgen. Zum Beispiel atomares Vergleichen und Austauschen einer speicherinternen Ressource oder Ereignisbeschaffung von persistierten Daten.
Aaron M. Eshbach

@ AaronM.Eshbach, stimmte zu, und danke, dass Sie diese angerufen haben.
Erik Eidt

1

Es liegt beim Anwendungsentwickler, das E-Tag tatsächlich zu überprüfen und diese Logik bereitzustellen. Es ist keine Zauberei, die der Webserver für Sie erledigt, da er nur weiß, wie E-TagHeader für statische Inhalte berechnet werden. Nehmen wir also Ihr Szenario oben und teilen Sie auf, wie die Interaktion stattfinden soll.

GET /projects/1

Der Server empfängt die Anforderung, ermittelt das E-Tag für diese Version des Datensatzes und gibt dieses mit dem tatsächlichen Inhalt zurück.

200 - OK
E-Tag: "412"
Content-Type: application/json
{modified: false}

Da der Client jetzt den E-Tag-Wert hat, kann er diesen in die PUTAnforderung aufnehmen:

PUT /projects/1
If-Match: "412"
Content-Type: application/json
{modified: true}

Zu diesem Zeitpunkt muss Ihre Anwendung Folgendes tun:

  • Stellen Sie sicher, dass das E-Tag noch korrekt ist: "412" == "412"?
  • Wenn ja, nehmen Sie das Update vor und berechnen Sie ein neues E-Tag

Senden Sie die Erfolgsantwort.

204 No Content
E-Tag: "543"

Wenn eine andere Anforderung eingeht und versucht, eine PUTähnliche Anforderung wie oben auszuführen, müssen Sie die Fehlermeldung bereitstellen, wenn Ihr Servercode sie zum zweiten Mal auswertet.

  • Stellen Sie sicher, dass das E-Tag noch korrekt ist: "412"! = "543"

Senden Sie bei einem Fehler die Fehlerantwort.

412 Precondition Failed

Dies ist Code, den Sie tatsächlich schreiben müssen. Das E-Tag kann tatsächlich ein beliebiger Text sein (innerhalb der in der HTTP-Spezifikation definierten Grenzen). Es muss keine Zahl sein. Es kann auch ein Hashwert sein.


Dies ist keine Standard-HTTP-Notation, die Sie hier verwenden. In standardkonformem HTTP verwenden Sie ETag nur in einem Antwortheader. Sie senden niemals ETag in einem Anforderungsheader, sondern verwenden stattdessen den zuvor erfassten ETag-Wert in einem If-Match- oder If-None-Match-Header in Anforderungsheadern.
Lie Ryan

-2

Als Ergänzung zu den anderen Antworten werde ich eines der besten Zitate in der ZeroMQ-Dokumentation veröffentlichen , das das zugrunde liegende Problem genau beschreibt:

Um absolut perfekte MT-Programme zu erstellen (und das meine ich wörtlich), benötigen wir keine Mutexe, Sperren oder andere Formen der Kommunikation zwischen Threads, außer Nachrichten, die über ZeroMQ-Sockets gesendet werden.

Mit "perfekten MT-Programmen" meine ich Code, der einfach zu schreiben und zu verstehen ist, mit dem gleichen Entwurfsansatz in jeder Programmiersprache und auf jedem Betriebssystem funktioniert und der über eine beliebige Anzahl von CPUs mit null Wartezuständen und ohne Punkt skaliert von sinkenden Renditen.

Wenn Sie jahrelang Tricks gelernt haben, mit denen Ihr MT-Code überhaupt funktioniert, geschweige denn schnell, mit Sperren und Semaphoren und kritischen Abschnitten, werden Sie angewidert sein, wenn Sie feststellen, dass alles umsonst war. Wenn es eine Lektion gibt, die wir aus mehr als 30 Jahren gleichzeitiger Programmierung gelernt haben, dann ist es: Teilen Sie den Status einfach nicht. Es ist wie wenn zwei Säufer versuchen, ein Bier zu teilen. Es ist egal, ob sie gute Freunde sind. Früher oder später werden sie in einen Kampf geraten. Und je mehr Betrunkene Sie dem Tisch hinzufügen, desto mehr streiten sie sich um das Bier. Die tragische Mehrheit der MT-Anwendungen sieht aus wie betrunkene Kneipenkämpfe.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.