web-dev-qa-db-fra.com

Comment supprimer atomiquement les clés correspondant à un motif en utilisant Redis

Dans ma base de données Redis, j'ai un certain nombre de hashs prefix:<numeric_id>.

Parfois, je veux tous les purger de manière atomique. Comment faire cela sans utiliser un mécanisme de verrouillage distribué?

530
Alexander Gladysh

À partir de Redis 2.6.0, vous pouvez exécuter des scripts lua, qui s'exécutent de manière atomique. Je n'en ai jamais écrit, mais je pense que cela ressemblerait à quelque chose comme ça

EVAL "return redis.call('del', unpack(redis.call('keys', ARGV[1])))" 0 prefix:[YOUR_PREFIX e.g delete_me_*]

Voir le documentation EVAL .

403
mcdizzle

Exécuter en bash:

redis-cli KEYS "prefix:*" | xargs redis-cli DEL

UPDATE

Ok j'ai compris. Qu'en est-il de cette façon: stockez le préfixe incrémental supplémentaire actuel et ajoutez-le à toutes vos clés. Par exemple:

Vous avez des valeurs comme celle-ci:

prefix_prefix_actuall = 2
prefix:2:1 = 4
prefix:2:2 = 10

Lorsque vous devez purger des données, vous modifiez d'abord prefix_actuall (par exemple, set prefix_prefix_actuall = 3). Votre application écrira donc de nouvelles données dans les clés prefix: 3: 1 et prefix: 3: 2. Ensuite, vous pouvez prendre en toute sécurité les anciennes valeurs de préfixe: 2: 1 et préfixe: 2: 2 et purger les anciennes clés.

671
Casey

Voici une version entièrement fonctionnelle et atomique d'une suppression générique mise en œuvre dans Lua. Il fonctionnera beaucoup plus rapidement que la version xargs en raison de la réduction beaucoup moins importante des allers-retours sur le réseau. Il est totalement atomique et bloque toute autre requête contre redis jusqu'à la fin. Si vous souhaitez supprimer les clés de manière atomique sur Redis 2.6.0 ou une version ultérieure, optez pour cette solution:

redis-cli -n [some_db] -h [some_Host_name] EVAL "return redis.call('DEL', unpack(redis.call('KEYS', ARGV[1] .. '*')))" 0 prefix:

Ceci est une version de travail de l'idée de @ mcdizzle dans sa réponse à cette question. Le crédit pour l'idée lui revient à 100%.

EDIT: Par commentaire de Kikito ci-dessous, si vous avez plus de clés à supprimer que de mémoire libre sur votre serveur Redis, vous rencontrerez le erreur "trop ​​d'éléments à décompresser" . Dans ce cas, faites-vous:

for _,k in ipairs(redis.call('keys', ARGV[1])) do 
    redis.call('del', k) 
end

Comme suggéré par Kikito.

70
Eli

Avertissement: la solution suivante ne fournit pas d'atomicité .

À partir de la v2.8, vous voulez vraiment utiliser la commande SCAN au lieu de KEYS [1]. Le script Bash suivant illustre la suppression de clés par modèle:

#!/bin/bash

if [ $# -ne 3 ] 
then
  echo "Delete keys from Redis matching a pattern using SCAN & DEL"
  echo "Usage: $0 <Host> <port> <pattern>"
  exit 1
fi

cursor=-1
keys=""

while [ $cursor -ne 0 ]; do
  if [ $cursor -eq -1 ]
  then
    cursor=0
  fi

  reply=`redis-cli -h $1 -p $2 SCAN $cursor MATCH $3`
  cursor=`expr "$reply" : '\([0-9]*[0-9 ]\)'`
  keys=${reply##[0-9]*[0-9 ]}
  redis-cli -h $1 -p $2 DEL $keys
done

[1] KEYS est une commande dangereuse pouvant éventuellement entraîner un déni de service. Ce qui suit est une citation de sa page de documentation:

Avertissement: considère KEYS comme une commande qui ne doit être utilisée que dans des environnements de production avec une extrême prudence. Cela risque de nuire aux performances lorsqu'il est exécuté sur des bases de données volumineuses. Cette commande est destinée au débogage et à des opérations spéciales, telles que la modification de la disposition de votre espace clé. N'utilisez pas KEYS dans votre code d'application habituel. Si vous cherchez un moyen de rechercher des clés dans un sous-ensemble de votre espace de clés, envisagez d'utiliser des ensembles.

UPDATE: une ligne pour le même effet de base -

$ redis-cli --scan --pattern "*:foo:bar:*" | xargs -L 100 redis-cli DEL
60
Itamar Haber

Pour ceux qui avaient du mal à analyser d'autres réponses:

eval "for _,k in ipairs(redis.call('keys','key:*:pattern')) do redis.call('del',k) end" 0

Remplacez key:*:pattern par votre propre modèle et entrez-le dans redis-cli et vous pourrez continuer.

Crédit lisco de: http://redis.io/commands/del

36
randomor

J'utilise la commande ci-dessous dans Redis 3.2.8

_redis-cli KEYS *YOUR_KEY_PREFIX* | xargs redis-cli DEL
_

Vous pouvez obtenir davantage d’aide sur la recherche de modèle de clé à partir d’ici: - https://redis.io/commands/keys . Utilisez votre modèle pratique de style glob selon vos besoins, tel que *YOUR_KEY_PREFIX* ou YOUR_KEY_PREFIX?? ou tout autre.

Et si l’un d’entre vous a intégré la bibliothèque Redis PHP que celle ci-dessous vous aidera.

_flushRedisMultipleHashKeyUsingPattern("*YOUR_KEY_PATTERN*"); //function call

function flushRedisMultipleHashKeyUsingPattern($pattern='')
        {
            if($pattern==''){
                return true;
            }

            $redisObj = $this->redis;
            $getHashes = $redisObj->keys($pattern);
            if(!empty($getHashes)){
                $response = call_user_func_array(array(&$redisObj, 'del'), $getHashes); //setting all keys as parameter of "del" function. Using this we can achieve $redisObj->del("key1","key2);
            }
        }
_

Merci :)

35

La solution de @ mcdizle ne fonctionne pas, elle ne fonctionne que pour une entrée.

Celui-ci fonctionne pour toutes les clés avec le même préfixe

EVAL "for i, name in ipairs(redis.call('KEYS', ARGV[1])) do redis.call('DEL', name); end" 0 prefix*

Note: Vous devriez remplacer 'prefix' par votre préfixe de clé ...

18
efaruk

Vous pouvez également utiliser cette commande pour supprimer les clés: -

Supposons qu'il y ait plusieurs types de clés dans votre redis, comme

  1. 'xyz_category_fpc_12'
  2. 'xyz_category_fpc_245'
  3. 'xyz_category_fpc_321'
  4. 'xyz_product_fpc_876'
  5. 'xyz_product_fpc_302'
  6. 'xyz_product_fpc_01232'

Ex- 'xyz_category_fpc' ici xyz est un nom du site, et ces clés sont liées aux produits et aux catégories d'un site de commerce électronique et sont générées par FPC.

Si vous utilisez cette commande comme ci-dessous

redis-cli --scan --pattern 'key*' | xargs redis-cli del

OR

redis-cli --scan --pattern 'xyz_category_fpc*' | xargs redis-cli del

Il supprime toutes les clés telles que 'xyz_category_fpc' (supprime les clés 1, 2 et 3). Pour supprimer les autres touches numériques 4, 5 et 6, utilisez la commande 'xyz_product_fpc' dans la commande ci-dessus.

Si vous voulez Supprimer tout dans Redis, alors suivez ces commandes-

Avec redis-cli:

  1. FLUSHDB - Supprime les données de la base de données CURRENT de votre connexion.
  2. FLUSHALL - Supprime les données de TOUTES les bases de données.

Par exemple: - dans votre shell:

redis-cli flushall
redis-cli flushdb
18
Vishal Thakur

Si vous avez un espace dans le nom des clés, vous pouvez l'utiliser en bash:

redis-cli keys "pattern: *" | xargs -L1 -I '$' echo '"$"' | xargs redis-cli del
13
Inc33

La réponse de @ itamar est excellente, mais l'analyse de la réponse ne fonctionnait pas pour moi, en particulier. dans le cas où aucune clé ne se trouve dans un scan donné. Une solution éventuellement plus simple, directement depuis la console:

redis-cli -h Host -p PORT  --scan --pattern "prefix:*" | xargs -n 100 redis-cli DEL

Cela utilise également SCAN, qui est préférable à KEYS en production, mais n’est pas atomique.

10
Guitan

Je viens d'avoir le même problème. J'ai stocké les données de session pour un utilisateur au format suivant:

session:sessionid:key-x - value of x
session:sessionid:key-y - value of y
session:sessionid:key-z - value of z

Ainsi, chaque entrée était une paire clé-valeur séparée. Lorsque la session est détruite, je souhaitais supprimer toutes les données de la session en supprimant les clés avec le motif session:sessionid:* - mais redis n’a pas cette fonction.

Ce que j'ai fait: stocker les données de session dans un hash . Je viens de créer un hachage avec l'identifiant de hachage session:sessionid, puis j'appuie key-x, key-y, key-z dans ce hachage (l'ordre ne m'importait pas) et si Je n'ai plus besoin de ce hachage Je fais juste un DEL session:sessionid et toutes les données associées à cet identifiant de hachage ont disparu. DEL est atomique et l'accès aux données/écriture de données dans le hachage est O (1).

8
Max

FYI.

  • en utilisant seulement bash et redis-cli
  • ne pas utiliser keys (ceci utilise scan)
  • fonctionne bien en mode cluster
  • pas atomique

Peut-être avez-vous seulement besoin de modifier les majuscules.

scan-match.sh

#!/bin/bash
rcli=“/YOUR_PATH/redis-cli" 
default_server="YOUR_SERVER"
default_port="YOUR_PORT"
servers=`$rcli -h $default_server -p $default_port cluster nodes | grep master | awk '{print $2}' | sed 's/:.*//'`
if [ x"$1" == "x" ]; then 
    startswith="DEFAULT_PATTERN"
else
    startswith="$1"
fi
MAX_BUFFER_SIZE=1000
for server in $servers; do 
    cursor=0
    while 
        r=`$rcli -h $server -p $default_port scan $cursor match "$startswith*" count $MAX_BUFFER_SIZE `
        cursor=`echo $r | cut -f 1 -d' '`
        nf=`echo $r | awk '{print NF}'`
        if [ $nf -gt 1 ]; then
            for x in `echo $r | cut -f 1 -d' ' --complement`; do 
                echo $x
            done
        fi
        (( cursor != 0 ))
    do
        :
    done
done

clear-redis-key.sh

#!/bin/bash
STARTSWITH="$1"

RCLI=YOUR_PATH/redis-cli
Host=YOUR_Host
PORT=6379
RCMD="$RCLI -h $Host -p $PORT -c "

./scan-match.sh $STARTSWITH | while read -r KEY ; do
    $RCMD del $KEY 
done

Courir à l'invite

$ ./clear-redis-key.sh key_head_pattern
6
plhn

Je pense que ce qui pourrait vous aider est le MULTI/EXEC/DISCARD . Bien que non équivalent à 100% des transactions , vous devriez pouvoir isoler les suppressions d'autres mises à jour.

5
alexpopescu

Veuillez utiliser cette commande et essayez:

redis-cli --raw keys "$PATTERN" | xargs redis-cli del
4
Sufiyan Malek

Il est simple à mettre en œuvre via la fonctionnalité "Supprimer la branche" dans FastoRedis , il suffit de sélectionner la branche que vous souhaitez supprimer.

enter image description here

4
Topilski Alexandr

Une version utilisant SCAN plutôt que KEYS (comme recommandé pour les serveurs de production) et --pipe plutôt que xargs.

Je préfère utiliser pipe plutôt que xargs, car il est plus efficace et fonctionne lorsque vos clés contiennent des guillemets ou d’autres caractères spéciaux que votre shell essaie et interprète. La substitution de regex dans cet exemple encapsule la clé entre guillemets et échappe aux guillemets internes.

export REDIS_Host=your.hostname.com
redis-cli -h "$REDIS_Host" --scan --pattern "YourPattern*" > /tmp/keys
time cat /tmp/keys | Perl -pe 's/"/\\"/g;s/^/DEL "/;s/$/"/;'  | redis-cli -h "$REDIS_Host" --pipe
3
tekumara

Ce n'est pas une réponse directe à la question, mais puisque je suis arrivé ici en cherchant mes propres réponses, je vais le partager ici.

Si vous devez faire correspondre des dizaines, voire des centaines de millions de clés, les réponses données ici feront en sorte que Redis ne répondra pas pendant un temps assez long (minutes?), Et risque de planter en raison de la consommation de mémoire (soyez sûr, la sauvegarde en arrière-plan sera coup de poing au milieu de votre opération).

L’approche suivante est indéniablement moche, mais je n’en ai pas trouvé de meilleure. Atomicity est hors de question ici, dans ce cas, l’objectif principal est de maintenir Redis actif et réactif 100% du temps. Cela fonctionnera parfaitement si vous avez toutes vos clés dans l'une des bases de données et que vous n'avez pas besoin de faire correspondre un modèle, mais que vous ne pouvez pas utiliser http://redis.io/commands/FLUSHDB à cause de son blocage la nature.

L'idée est simple: écrivez un script qui s'exécute en boucle et utilise O(1) opération comme http://redis.io/commands/SCAN ou http : //redis.io/commands/RANDOMKEY pour obtenir les clés, vérifie si elles correspondent au modèle (si vous en avez besoin) et http://redis.io/commands/DEL les un par un.

S'il y a une meilleure façon de le faire, s'il vous plaît faites le moi savoir, je mettrai à jour la réponse.

Exemple d’implémentation avec randomkey en Ruby, en tant que tâche rake, substitut non bloquant de quelque chose comme redis-cli -n 3 flushdb:

desc 'Cleanup redis'
task cleanup_redis: :environment do
  redis = Redis.new(...) # connection to target database number which needs to be wiped out
  counter = 0
  while key = redis.randomkey               
    puts "Deleting #{counter}: #{key}"
    redis.del(key)
    counter += 1
  end
end
2
Spajus

J'ai essayé la plupart des méthodes mentionnées ci-dessus mais elles ne fonctionnaient pas pour moi. Après quelques recherches, j'ai trouvé les points suivants:

  • si vous avez plus d'une base de données sur Redis, vous devez déterminer la base de données à l'aide de -n [number].
  • si vous avez quelques clés, utilisez del mais s'il y a des milliers ou des millions de clés, il vaut mieux utiliser unlink car , le lien est non bloquant pendant que del bloque, pour plus d'informations, visitez cette page nlink vs del
  • aussi keys sont comme del et bloque

j'ai donc utilisé ce code pour supprimer des clés par modèle:

 redis-cli -n 2 --scan --pattern '[your pattern]' | xargs redis-cli -n 2 unlink 
1
mahdi yousefi

Je soutiens toutes les réponses liées à avoir un outil ou à exécuter une expression Lua.

Une autre option de mon côté:

Nos bases de données de production et de pré-production contiennent des milliers de clés. De temps en temps, nous devons supprimer certaines clés (par un masque), les modifier selon certains critères, etc. Bien sûr, il n’est pas possible de le faire manuellement à partir de la CLI, en particulier en ayant un sharding (512 dbs logiques dans chaque physique).

À cette fin, j'écris Java outil client qui fait tout ce travail. En cas de suppression de clés, l'utilitaire peut être très simple, avec une seule classe:

public class DataCleaner {

    public static void main(String args[]) {
        String keyPattern = args[0];
        String Host = args[1];
        int port = Integer.valueOf(args[2]);
        int dbIndex = Integer.valueOf(args[3]);

        Jedis jedis = new Jedis(Host, port);

        int deletedKeysNumber = 0;
        if(dbIndex >= 0){
            deletedKeysNumber += deleteDataFromDB(jedis, keyPattern, dbIndex);
        } else {
            int dbSize = Integer.valueOf(jedis.configGet("databases").get(1));
            for(int i = 0; i < dbSize; i++){
                deletedKeysNumber += deleteDataFromDB(jedis, keyPattern, i);
            }
        }

        if(deletedKeysNumber == 0) {
            System.out.println("There is no keys with key pattern: " + keyPattern + " was found in database with Host: " + Host);
        }
    }

    private static int deleteDataFromDB(Jedis jedis, String keyPattern, int dbIndex) {
        jedis.select(dbIndex);
        Set<String> keys = jedis.keys(keyPattern);
        for(String key : keys){
            jedis.del(key);
            System.out.println("The key: " + key + " has been deleted from database index: " + dbIndex);
        }

        return keys.size();
    }

}
0
Denys