Frage an redis – Transaktionen und Watch Statement in Redis

30

Könnten Sie mir bitte folgendes Beispiel aus "The Little Redis Book" erklären:

Mit dem obigen Code könnten wir unseren eigenen Befehl incr nicht implementieren, da sie alle zusammen ausgeführt werden, sobald exec aufgerufen wird. Aus dem Code können wir nicht machen:

redis.multi() 
current = redis.get('powerlevel') 
redis.set('powerlevel', current + 1) 
redis.exec()

So funktionieren Redis-Transaktionen nicht. Wenn wir jedoch eine Uhr zu powerlevel hinzufügen, können wir Folgendes tun:

redis.watch('powerlevel') 
current = redis.get('powerlevel') 
redis.multi() 
redis.set('powerlevel', current + 1) 
redis.exec()

Wenn ein anderer Client den Wert von powerlevel ändert, nachdem wir watch on it aufgerufen haben, schlägt unsere Transaktion fehl. Wenn kein Client den Wert ändert, funktioniert das Set. Wir können diesen Code in einer Schleife ausführen, bis es funktioniert.

Warum können wir kein Inkrement in einer Transaktion ausführen, die nicht durch einen anderen Befehl unterbrochen werden kann? Warum müssen wir stattdessen iterieren und warten, bis niemand Wert ändertVor Transaktion beginnt?

@polvoazul, ich kenne diesen Befehl, danke. Es war eine häufige Frage, die nicht durch einen echten Fall verursacht wurde. Marboni
Sie kennen dasinkr comand in redis richtig? Es macht genau das, was Sie in Ihrem Beispiel wollen, ohne eine Transaktion zu verwenden. Natürlich ist dies keine Antwort auf die eigentliche Frage, aber es lohnt sich trotzdem zu wissen. polvoazul

Deine Antwort

1   die antwort
70

1) Warum können wir kein Inkrement in einer Transaktion ausführen, die nicht durch einen anderen Befehl unterbrochen werden kann?

Bitte beachten Sie zuerst, dass Redis "Transaktionen" völlig anders sind als die meisten Leute denken, dass Transaktionen in klassischem 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 clientseitig (in Ihrem Skript) ausgeführt wird. Im obigen Code werden die Befehle GET und SET auf der Redis-Seite ausgeführt, die Zuweisung zu current und die Berechnung von current + 1 sollen jedoch auf der Client-Seite ausgeführt werden.

Um die Atomizität zu gewährleisten, verzögert ein MULTI / EXEC-Block die Ausführung von Redis-Befehlen bis zur Ausführung. Der Client stapelt also nur die Befehle GET und SET im Speicher und führt sie auf einmal und am Ende atomar aus. Natürlich wird der Versuch, dem Ergebnis von GET und Inkrementierung einen aktuellen Wert zuzuweisen, lange vorher unternommen. 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 funktioniert nicht.

In MULTI / EXEC-Blöcken können Sie nur Befehle verwenden, deren Parameter vor dem Beginn des Blocks vollständig bekannt sind. Vielleicht möchten Sie lesendie Dokumentation für mehr Informationen.

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 keine WATCH / MULTI / EXEC verwenden würden, hätten wir eine potenzielle Rennbedingung:

# 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

Fügen wir nun einen WATCH / MULTI / EXEC-Block hinzu. 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, bis niemand den Wert ändert, sondern immer wieder versuchen, die Operation durchzuführen, bis Redis sicher ist, dass die Werte konsistent sind und signalisiert, dass sie erfolgreich sind.

In den meisten Fällen sind die Aktualisierungen sehr effizient, wenn die "Transaktionen" schnell genug sind und die Wahrscheinlichkeit von Konflikten gering ist. Wenn es nun zu Konflikten kommt, müssen einige zusätzliche Operationen für einige "Transaktionen" durchgeführt werden (aufgrund der Iteration und der Wiederholungsversuche). Die Daten sind jedoch immer konsistent und es ist keine Sperre erforderlich.

@DidierSpezia Kann ich nach GET WATCH machen? Ich sehe keinen Grund, den Schlüssel zu beobachten, während ich den Wert lese mtkachenko
@DidierSpezia: Müssen wir aufpassenpowerlevel wieder in Sitzung B? Oder wiederholen Sie einfach den Transaktionsteil? cold_coder
Sie müssen powerlevel erneut ansehen, da Sie nicht garantieren können, dass eine dritte Sitzung nicht gleichzeitig mit powerlevel in Konflikt gerät. Didier Spezia
Tolle Erklärung, vielen Dank, endlich habe ich es verstanden! Nach dem Verständnis von Punkt 1 wird Punkt 2 offensichtlich. Marboni

Verwandte Fragen