Ci sono diverse domande qui.
1) Perché non è possibile eseguire l'incremento nella transazione che non può essere interrotto da un altro comando?
Si noti innanzitutto che le "transazioni" di Redis sono completamente diverse da quelle che la maggior parte della gente pensa che le transazioni siano nel DBMS classico.
# Does not work
redis.multi()
current = redis.get('powerlevel')
redis.set('powerlevel', current + 1)
redis.exec()
è necessario capire che cosa viene eseguito sul lato server (in Redis), e ciò che viene eseguito sul lato client (nello script). Nel codice sopra, i comandi GET e SET verranno eseguiti sul lato Redis, ma l'assegnazione alla corrente e il calcolo di +1 corrente dovrebbero essere eseguiti sul lato client.
Per garantire l'atomicità, un blocco MULTI/EXEC ritarda l'esecuzione dei comandi Redis fino all'esec. Quindi il client accumulerà solo i comandi GET e SET in memoria, e li eseguirà in un colpo solo e atomicamente alla fine. Naturalmente, il tentativo di assegnare corrente al risultato di GET e l'incremento si verificherà molto prima. In realtà il metodo redis.get restituirà solo la stringa "QUEUED" per segnalare che il comando è in ritardo e l'incremento non funzionerà.
Nei blocchi MULTI/EXEC è possibile utilizzare solo i comandi i cui parametri possono essere completamente noti prima dell'inizio del blocco. Si consiglia di leggere the documentation per ulteriori informazioni.
2) Perché è necessario iterare invece e attendere fino a quando nessuno cambia valore prima dell'inizio della transazione?
Questo è un esempio di modello ottimistico concorrente.
Se abbiamo usato nessun orologio/MULTI/EXEC, si avrebbe un potenziale condizione di gara:
# 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
Ora aggiungiamo un OROLOGIO/MULTI/blocco EXEC. Con una clausola WATCH, i comandi tra MULTI ed EXEC vengono eseguiti solo se il valore non è cambiato.
# 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.
Quindi non c'è bisogno di iterare aspettare fino a nessuno cambia il valore, ma piuttosto di tentare l'operazione ancora e ancora fino Redis è che i valori siano coerenti e segnali che è successo.
Nella maggior parte dei casi, se le "transazioni" sono abbastanza veloci e la probabilità di contesa è bassa, gli aggiornamenti sono molto efficienti. Ora, se c'è contesa, alcune operazioni extra dovranno essere fatte per alcune "transazioni" (a causa dell'iterazione e dei tentativi). Ma i dati saranno sempre coerenti e non è richiesto alcun blocco.
Sei a conoscenza del comando [incr] (http://redis.io/commands/incr) in redis, giusto? Fa esattamente quello che vuoi nel tuo esempio, senza usare una transazione. Ovviamente questa non è una risposta alla domanda stessa, ma comunque vale la pena saperlo. – polvoazul
@polvoazul, conosco questo comando, grazie. Era una domanda comune non causata da casi reali. – Marboni