Hier gibt es mehrere Fragen.
1) Warum können wir kein Inkrement in Transaktionen ausführen, die nicht durch andere Befehle unterbrochen werden können?
Bitte beachten Sie zunächst, dass Redis "Transaktionen" völlig anders sind als die meisten Leute denken, dass Transaktionen in klassischen DBMS sind.
# Does not work
redis.multi()
current = redis.get('powerlevel')
redis.set('powerlevel', current + 1)
redis.exec()
Sie müssen verstehen, was serverseitig (in Redis) und was clientseitig (in Ihrem Skript) ausgeführt wird. Im obigen Code werden die GET- und SET-Befehle auf Redis-Seite ausgeführt, aber die Zuweisung zu Strom und die Berechnung von Strom + 1 sollen auf Client-Seite ausgeführt werden.
Um Atomarität zu gewährleisten, verzögert ein MULTI/EXEC-Block die Ausführung von Redis-Befehlen, bis die exec. Der Client stapelt also nur die GET- und SET-Befehle im Speicher und führt sie am Ende auf einmal und atomar aus. Natürlich wird der Versuch, dem Ergebnis von GET und der Inkrementierung Strom zuzuweisen, lange vorher erfolgen. Tatsächlich gibt die redis.get-Methode nur die Zeichenfolge "QUEUED" zurück, um zu signalisieren, dass der Befehl verzögert ist, und die Inkrementierung wird nicht funktionieren.
In MULTI/EXEC-Blöcken können Sie nur Befehle verwenden, deren Parameter vor Beginn des Blocks vollständig bekannt sein können. Vielleicht möchten Sie die Dokumentation für weitere Informationen lesen.
2) Warum müssen wir stattdessen iterieren und warten, bis niemand den Wert ändert, bevor die Transaktion beginnt?
Dies ist ein Beispiel für ein gleichzeitiges optimistisches Muster.
Wenn wir kein WATCH/MULTI/EXEC verwenden würden, hätten wir eine potenzielle Race-Condition:
# Initial arbitrary value
powerlevel = 10
session A: GET powerlevel -> 10
session B: GET powerlevel -> 10
session A: current = 10 + 1
session B: current = 10 + 1
session A: SET powerlevel 11
session B: SET powerlevel 11
# In the end we have 11 instead of 12 -> wrong
Lassen Sie uns nun einen WATCH/MULTI/EXEC-Block hinzufügen. Mit einer WATCH-Klausel werden die Befehle zwischen MULTI und EXEC nur ausgeführt, wenn sich der Wert nicht geändert hat.
# Initial arbitrary value
powerlevel = 10
session A: WATCH powerlevel
session B: WATCH powerlevel
session A: GET powerlevel -> 10
session B: GET powerlevel -> 10
session A: current = 10 + 1
session B: current = 10 + 1
session A: MULTI
session B: MULTI
session A: SET powerlevel 11 -> QUEUED
session B: SET powerlevel 11 -> QUEUED
session A: EXEC -> success! powerlevel is now 11
session B: EXEC -> failure, because powerlevel has changed and was watched
# In the end, we have 11, and session B knows it has to attempt the transaction again
# Hopefully, it will work fine this time.
Sie müssen also nicht iterieren, um zu warten, bis niemand den Wert ändert, sondern den Vorgang immer wieder versuchen, bis Redis sicher ist, dass die Werte konsistent sind, und signalisiert, dass es erfolgreich ist.
Wenn die "Transaktionen" schnell genug sind und die Wahrscheinlichkeit von Konflikten gering ist, sind die Aktualisierungen in den meisten Fällen sehr effizient. Wenn es nun Konflikte gibt, müssen einige zusätzliche Operationen für einige "Transaktionen" durchgeführt werden (aufgrund der Iteration und der Wiederholungen). Aber die Daten sind immer konsistent und es ist keine Sperrung erforderlich.