2012-05-25 12 views
24

La prego di spiegare seguente esempio da "The Little Redis Book":Transazioni e dichiarazione orologio in Redis

Con il codice di cui sopra, non saremmo in grado di implementare il nostro comando proprio incr dal momento che sono tutti eseguiti insieme una volta che viene chiamato exec. Da codice, non possiamo fare:

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

che non è come Redis funzionano le transazioni. Ma, se si aggiunge un orologio a powerlevel, possiamo fare:

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

Se un altro client cambia il valore di powerlevel dopo abbiamo chiamato orologio su di esso, la nostra transazione avrà esito negativo. Se nessun client modifica il valore , la serie funzionerà. Possiamo eseguire questo codice in un ciclo fino a quando non funziona .

Perché non è possibile eseguire l'incremento nella transazione che non può essere interrotto da un altro comando? Perché abbiamo bisogno di iterare invece e attendere fino a quando nessuno cambia il valore prima dell'inizio della transazione?

+0

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

+2

@polvoazul, conosco questo comando, grazie. Era una domanda comune non causata da casi reali. – Marboni

risposta

62

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.

+1

Ottima spiegazione, grazie mille, finalmente ce l'ho fatta! Dopo aver compreso l'articolo 1, l'elemento 2 diventa ovvio. – Marboni

+0

Quindi, c'è qualche "equità" in questo? Cosa succede se ho fatto "l'orologio" ma non è riuscito? Quindi devo riprovare. E se ci fosse stata una contesa, potrei mai provare per sempre senza "ottenere il lucchetto"? – Brad

+0

Quindi penso di aver capito dal tuo esempio: se hai mostrato un esempio che ha MULTI/EXEC ma nessun WATCH, entrambe le sessioni potrebbero leggere il valore 10, e avere Redis riscrivere 11. Avrebbe fatto il writeback "atomico", ma non proteggerebbe il client leggendo e incrementando lo stesso valore localmente, risultando in una risposta errata? – Brad