Pregunta sobre redis – Transacciones y declaración de reloj en Redis

30

¿Podría explicarme el siguiente ejemplo de "The Little Redis Book":

Con el código anterior, no podríamos implementar nuestro propio comando incr ya que todos se ejecutan juntos una vez que se llama a exec. Desde el código, no podemos hacer:

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

Así no es como funcionan las transacciones Redis. Pero, si agregamos un reloj a powerlevel, podemos hacer:

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

Si otro cliente cambia el valor de powerlevel después de que hayamos llamado a verlo, nuestra transacción fallará. Si ningún cliente cambia el valor, el conjunto funcionará. Podemos ejecutar este código en un bucle hasta que funcione.

¿Por qué no podemos ejecutar incrementos en transacciones que no pueden ser interrumpidas por otro comando? ¿Por qué necesitamos iterar y esperar hasta que nadie cambie de valor?antes de comienza la transacción?

Usted es consciente de laincrustar comandar en redis verdad? Hace exactamente lo que quiere en su ejemplo, sin utilizar una transacción. Por supuesto, esto no es una respuesta a la pregunta en sí, pero sin embargo vale la pena saberlo. polvoazul
@polvoazul, conozco este comando, gracias. Era una pregunta común no causada por un caso real. Marboni

Tu respuesta

1   la respuesta
70

Hay varias preguntas aquí.

1) ¿Por qué no podemos ejecutar incrementos en transacciones que no pueden ser interrumpidas por otro comando?

En primer lugar, tenga en cuenta que las "transacciones" de Redis son completamente diferentes de lo que la mayoría de la gente piensa que las transacciones son en el DBMS clásico.

# Does not work
redis.multi() 
current = redis.get('powerlevel') 
redis.set('powerlevel', current + 1) 
redis.exec()

Debe comprender qué se ejecuta en el lado del servidor (en Redis) y qué se ejecuta en el lado del cliente (en su script). En el código anterior, los comandos GET y SET se ejecutarán en el lado Redis, pero se supone que la asignación a la corriente y el cálculo de la corriente + 1 se ejecutan en el lado del cliente.

Para garantizar la atomicidad, un bloque MULTI / EXEC retrasa la ejecución de los comandos de Redis hasta el exec. Por lo tanto, el cliente solo acumulará los comandos GET y SET en la memoria, y los ejecutará de una sola vez y al final atómicamente. Por supuesto, el intento de asignar corriente al resultado de GET y al incremento ocurrirá mucho antes. En realidad, el método redis.get solo devolverá la cadena "QUEUED" para indicar que el comando se retrasó, y el incremento no funcionará.

En los bloques MULTI / EXEC, solo puede usar comandos cuyos parámetros se puedan conocer completamente antes del comienzo del bloque. Puede que quieras leerla documentación para más información.

2) ¿Por qué necesitamos iterar y esperar hasta que nadie cambie de valor antes de que comience la transacción?

Este es un ejemplo de patrón optimista concurrente.

Si no usáramos WATCH / MULTI / EXEC, tendríamos una posible condición de carrera:

# 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

Ahora agreguemos un bloque WATCH / MULTI / EXEC. Con una cláusula WATCH, los comandos entre MULTI y EXEC se ejecutan solo si el valor no ha cambiado.

# 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.

Por lo tanto, no tiene que iterar para esperar hasta que nadie cambie el valor, sino que debe intentar la operación una y otra vez hasta que Redis esté seguro de que los valores son coherentes e indica que es exitoso.

En la mayoría de los casos, si las "transacciones" son lo suficientemente rápidas y la probabilidad de que haya contención es baja, las actualizaciones son muy eficientes. Ahora, si hay contención, habrá que realizar algunas operaciones adicionales para algunas "transacciones" (debido a la iteración y los reintentos). Pero los datos siempre serán consistentes y no se requiere bloqueo.

Debe volver a ver powerlevel, ya que no tiene garantía de que una tercera sesión no esté jugando con powerlevel al mismo tiempo. Didier Spezia
@DidierSpezia ¿Puedo crear WATCH después de GET? No veo una razón para comenzar a mirar la llave mientras leo el valor mtkachenko
@DidierSpezia: tenemos que verpowerlevel de nuevo en la sesión B? ¿O simplemente repetir la parte de la transacción? cold_coder
No hay garantía de equidad. Sin embargo, en la práctica, no es tan común que un cliente muera de hambre en una ejecución de watch / multi / exec. Didier Spezia

Preguntas relacionadas