Existe-t-il une structure de données Redis, qui permettrait une opération atomique consistant à décomposer (obtenir + supprimer) plusieurs éléments, qu’elle contient?
Il y a bien connu SPOP ou RPOP, mais ils renvoient toujours une valeur unique. Par conséquent, lorsque j'ai besoin des premières N valeurs de set/list, je dois appeler la commande N-times, ce qui est coûteux. Disons que l'ensemble/la liste contient des millions d'éléments. Existe-t-il quelque chose comme SPOPM "setName" 1000
, qui renverrait et supprimerait 1000 éléments aléatoires de set ou RPOPM "listName" 1000
, qui renverrait 1000 éléments les plus à droite de la liste?
Je sais qu'il existe des commandes telles que SRANDMEMBER et LRANGE, mais elles ne suppriment pas les éléments de la structure de données. Ils peuvent être supprimés séparément. Cependant, si plusieurs clients lisent à partir de la même structure de données, certains éléments peuvent être lus plusieurs fois et certains peuvent être supprimés sans lecture! Par conséquent, l'atomicité est le sujet de ma question.
En outre, je vais bien si la complexité en temps pour une telle opération est plus chère. Je doute que cela coûtera plus cher que d'émettre N (disons 1000, N de l'exemple précédent) des requêtes distinctes au serveur Redis.
Je connais aussi le support de transaction séparé. Cependant, cette phrase de Redis docs me dissuade de l’utiliser pour des processus parallèles modifiant l’ensemble (en le lisant de manière destructive):
Lors de l’utilisation de WATCH, EXEC n’exécutera les commandes que si les clés surveillées n’ont pas été modifiées, ce qui permet un mécanisme de vérification et de réglage.
À partir de Redis 3.2, la commande SPOP
a un argument [count]
pour extraire plusieurs éléments d'un ensemble.
Utilisez LRANGE
avec LTRIM
dans un pipeline . Le pipeline sera exécuté comme une transaction atomique. Votre inquiétude concernant WATCH
, EXEC
ci-dessus ne s'appliquera pas ici car vous exécutez les LRANGE
et LTRIM
comme une transaction sans possibilité pour aucune autre transaction d'un autre client de les séparer. Essaye le.
Pour développer la réponse d'Eli avec un exemple complet de collections de listes, utilisez les commandes intégrées lrange
et ltrim
au lieu de Lua:
127.0.0.1:6379> lpush a 0 1 2 3 4 5 6 7 8 9
(integer) 10
127.0.0.1:6379> lrange a 0 3 # read 4 items off the top of the stack
1) "9"
2) "8"
3) "7"
4) "6"
127.0.0.1:6379> ltrim a 4 -1 # remove those 4 items
OK
127.0.0.1:6379> lrange a 0 999 # remaining items
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
6) "0"
Si vous voulez rendre l'opération atomique, vous devez envelopper les commandes lrange et ltrim dans les commandes multi
et exec
.
De plus, comme indiqué ailleurs, vous devriez probablement ltrim
le nombre d'articles retournés pas le nombre d'articles que vous avez demandés. par exemple. si vous avez lrange a 0 99
mais que vous avez 50 éléments, vous ltrim a 50 -1
et non pas ltrim a 100 -1
.
Pour implémenter la sémantique de la file d'attente au lieu d'une pile, remplacez lpush
par rpush
.
si vous voulez un script lua, cela devrait être rapide et facile.
local result = redis.call('lrange',KEYS[1],0,ARGV[1]-1)
redis.call('ltrim',KEYS[1],ARGV[1],-1)
return result
alors vous n'avez pas à faire une boucle.
update: J'ai essayé de le faire avec srandmember (en 2.6) avec le script suivant:
local members = redis.call('srandmember', KEYS[1], ARGV[1])
redis.call('srem', KEYS[1], table.concat(table, ' '))
return members
mais je reçois une erreur:
error: -ERR Error running script (call to f_6188a714abd44c1c65513b9f7531e5312b72ec9b):
Write commands not allowed after non deterministic commands
Je ne sais pas si la version future le permettra, mais je suppose que non. Je pense que ce serait un problème de réplication.
Redis 4.0+ prend désormais en charge les modules qui ajoutent toutes sortes de nouvelles fonctionnalités et types de données avec un traitement beaucoup plus rapide et sûr que les scripts Lua ou les pipelines multi
exec
.
Redis Labs, le sponsor actuel de Redis, dispose d'un ensemble de modules d'extension utiles appelé _/redex ici: https://github.com/RedisLabsModules/redex
Le module rxlists
ajoute plusieurs opérations de liste, dont LMPOP
et RMPOP
, afin que vous puissiez extraire de manière atomique plusieurs valeurs d'une liste Redis. La logique est toujours O(n) (il s’agit de faire un simple clic dans une boucle), mais tout ce que vous avez à faire est d’installer le module une fois et d’envoyer simplement cette commande personnalisée. Je l’utilise sur des listes contenant des millions d’éléments et des milliers d’explosions en même temps, générant sans aucun problème plus de 500 Mo de trafic réseau.
Je pense que vous devriez regarder le support de LUA dans Redis. Si vous écrivez un script LUA et l'exécutez sur redis, il est garanti qu'il est atomique (car Redis est mono-threadé). Aucune requête ne sera exécutée avant la fin de votre script LUA (par exemple, vous ne pouvez pas implémenter une grosse tâche dans LUA, sinon redis sera lent).
Ainsi, dans ce script, vous ajoutez votre SPOP et votre RPOP, vous pouvez par exemple ajouter les résultats de chaque commande redis dans un tableau LUA, puis renvoyer le tableau à votre client Redis.
Ce que dit la documentation à propos de MULTI, c’est qu’il s’agit d’un verrouillage optimiste, c’est-à-dire qu’il va réessayer de faire le multi avec WATCH jusqu’à ce que la valeur surveillée ne soit pas modifiée. Si vous avez beaucoup d'écritures sur la valeur surveillée, cela sera plus lent que le verrouillage «pessimiste» (comme de nombreuses bases de données SQL: POSTGRESQL, MYSQL ...) qui, d'une certaine manière, "arrête le monde" pour que la requête soit exécutée en premier . Le verrouillage pessimiste n'est pas implémenté dans redis, mais vous pouvez l'implémenter si vous le souhaitez, mais il est complexe et vous n'en avez peut-être pas besoin (peu d'écritures sur cette valeur: optimiste devrait suffire).
vous pouvez probablement essayer un script lua (script.lua) comme ceci:
local result = {}
for i = 0 , ARGV[1] do
local val = redis.call('RPOP',KEYS[1])
if val then
table.insert(result,val)
end
end
return result
vous pouvez l'appeler de cette façon:
redis-cli eval "$(cat script.lua)" 1 "listName" 1000