Il y a plusieurs questions ici.
1) Pourquoi ne pouvons-nous pas exécuter d'incrément dans une transaction qui ne peut pas être interrompue par une autre commande ?
Veuillez d'abord noter que les "transactions" Redis sont complètement différentes de ce que la plupart des gens pensent que les transactions sont dans un SGBD classique.
# Does not work
redis.multi()
current = redis.get('powerlevel')
redis.set('powerlevel', current + 1)
redis.exec()
Vous devez comprendre ce qui est exécuté côté serveur (dans Redis) et ce qui est exécuté côté client (dans votre script). Dans le code ci-dessus, les commandes GET et SET seront exécutées côté Redis, mais l'affectation au courant et le calcul du courant + 1 sont supposés être exécutés côté client.
Pour garantir l'atomicité, un bloc MULTI/EXEC retarde l'exécution des commandes Redis jusqu'au exec. Ainsi, le client ne fera qu'empiler les commandes GET et SET en mémoire, et les exécutera en une seule fois et de manière atomique à la fin. Bien sûr, la tentative d'assignation de courant au résultat de GET et d'incrémentation aura lieu bien avant. En fait, la méthode redis.get ne renverra que la chaîne "QUEUED" pour signaler que la commande est retardée, et l'incrémentation ne fonctionnera pas.
Dans les blocs MULTI/EXEC, vous ne pouvez utiliser que des commandes dont les paramètres peuvent être entièrement connus avant le début du bloc. Vous voudrez peut-être lire la documentation pour plus d'informations.
2) Pourquoi avons-nous besoin d'itérer à la place et d'attendre que personne ne modifie la valeur avant le début de la transaction ?
Ceci est un exemple de modèle optimiste simultané.
Si nous n'utilisions pas WATCH/MULTI/EXEC, nous aurions une condition de concurrence potentielle :
# 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
Ajoutons maintenant un bloc WATCH/MULTI/EXEC. Avec une clause WATCH, les commandes entre MULTI et EXEC ne sont exécutées que si la valeur n'a pas changé.
# 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.
Vous n'avez donc pas besoin d'itérer pour attendre que personne ne modifie la valeur, mais plutôt de tenter l'opération encore et encore jusqu'à ce que Redis soit sûr que les valeurs sont cohérentes et signale qu'elle a réussi.
Dans la plupart des cas, si les "transactions" sont suffisamment rapides et que la probabilité d'avoir un conflit est faible, les mises à jour sont très efficaces. Maintenant, s'il y a conflit, certaines opérations supplémentaires devront être effectuées pour certaines "transactions" (en raison de l'itération et des tentatives). Mais les données seront toujours cohérentes et aucun verrouillage n'est requis.