Come eliminare atomicamente i tasti corrispondenti a un modello usando Redis


576

Nel mio Redis DB ho un certo numero di prefix:<numeric_id>hash.

A volte voglio eliminarli tutti atomicamente. Come posso farlo senza utilizzare un meccanismo di blocco distribuito?


Ciao Steve, c'è qualche problema con il mio sito web, l'ho aggiunto al mio altro blog mind-geek.net/nosql/redis/delete-keys-specific-expiry-time , spero che questo aiuti.
Gaurav Tewari,

43
Questo è uno scenario così comune che vorrei che il team Redis prendesse in considerazione l'aggiunta di un comando nativo per questo.
Todd Menier,

Oggi puoi farlo con Lua, vedi sotto.
Alexander Gladysh,

3
@ToddMenier Ho appena suggerito, ho ripreso questo ragionamento sul perché non accadrà mai: github.com/antirez/redis/issues/2042
Ray

1
Molte persone fanno domande correlate su come gestire un gran numero di chiavi, chiavi con caratteri speciali, ecc. Ho creato una domanda separata in quanto stiamo riscontrando questo problema ora e non credo che la risposta sia pubblicata su questa domanda. Ecco l'altra domanda: stackoverflow.com/questions/32890648/...
jakejgordon

Risposte:


431

A partire da redis 2.6.0, è possibile eseguire script lua, che vengono eseguiti atomicamente. Non ne ho mai scritto uno, ma penso che sarebbe simile a questo

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

Avvertenza : come dice il documento Redis , a causa delle performance maters, il keys comando non dovrebbe essere utilizzato per le normali operazioni di produzione, questo comando è destinato al debug e alle operazioni speciali. leggi di più

Vedi la documentazione EVAL .


23
Nota importante: questo non riesce se hai più di un paio di migliaia di chiavi corrispondenti al prefisso.
Nathan Osman,

93
Questo funziona per un gran numero di chiavi:EVAL "local keys = redis.call('keys', ARGV[1]) \n for i=1,#keys,5000 do \n redis.call('del', unpack(keys, i, math.min(i+4999, #keys))) \n end \n return keys" 0 prefix:*
sheerun,

181
Ouch ... redis è molto usato come semplice cache di chiavi / archivio. Questa sembra del prefix:* essere un'operazione fondamentale: /
Ray

5
@Ray francamente, se hai bisogno di quella funzione dovresti semplicemente partizionare i dati per database numerico o server e usare flush / flushdb
Marc Gravell

9
Sì, non riesce se nessuna chiave corrisponde al modello. Per risolvere il problema, ho aggiunto una chiave predefinita:EVAL "return redis.call('del', 'defaultKey', unpack(redis.call('keys', ARGV[1])))" 0 prefix:*
manuelmhtr

706

Eseguire in bash:

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

AGGIORNARE

Ok ho capito. Che dire in questo modo: memorizza il prefisso incrementale aggiuntivo corrente e aggiungilo a tutte le tue chiavi. Per esempio:

Hai valori come questo:

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

Quando è necessario eliminare i dati, è necessario innanzitutto modificare prefix_actuall (ad esempio impostare prefix_prefix_actuall = 3), quindi l'applicazione scriverà nuovi dati nei prefissi delle chiavi: 3: 1 e prefisso: 3: 2. Quindi puoi tranquillamente prendere i vecchi valori dal prefisso: 2: 1 e prefisso: 2: 2 ed eliminare le vecchie chiavi.


14
Siamo spiacenti, ma questa non è la cancellazione atomica. Qualcuno può aggiungere nuove chiavi tra KEYS e DEL. Non voglio cancellarli.
Alexander Gladysh,

36
Chiavi, che verranno create dopo il comando KEYS non verranno eliminate.
Casey,

6
Avevo solo bisogno di cancellare alcune chiavi sbagliate, quindi la prima risposta di Casey è stata perfetta, tranne per il fatto che ho dovuto spostare le chiavi fuori dalle virgolette: redis-cli KEYS "prefisso: *" | xargs redis-cli DEL
jslatts

19
Anche la prima risposta mi ha aiutato. Un'altra variante se le tue chiavi redis contengono virgolette o altri caratteri che redis-cli KEYS "prefix:*" | xargs --delim='\n' redis-cli DEL
confondono

18
Se si dispone di più database (spazi chiave), questo è il trucco: diciamo che è necessario eliminare le chiavi in ​​db3:redis-cli -n 3 KEYS "prefix:*" | xargs redis-cli -n 3 DEL
Christoffer,

73

Ecco una versione completamente funzionante e atomica di una cancellazione di caratteri jolly implementata in Lua. Funzionerà molto più velocemente della versione xargs a causa della molta meno rete avanti e indietro, ed è completamente atomico, bloccando qualsiasi altra richiesta contro redis fino al termine. Se vuoi eliminare atomicamente le chiavi su Redis 2.6.0 o versioni successive, questa è sicuramente la strada da percorrere:

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

Questa è una versione funzionante dell'idea di @mcdizzle nella sua risposta a questa domanda. Il merito dell'idea è al 100%.

EDIT: Secondo il commento di Kikito di seguito, se hai più chiavi da eliminare rispetto alla memoria libera nel tuo server Redis, ti imbatterai nell'errore "troppi elementi per decomprimere" . In tal caso, eseguire:

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

Come suggerito da Kikito.


10
Il codice sopra verrà visualizzato se hai un numero significativo di chiavi (l'errore è "troppi elementi da decomprimere"). Consiglio di utilizzare un loop sulla parte Lua:for _,k in ipairs(redis.call('keys', KEYS[1])) do redis.call('del', k) end
kikito

@kikito, sì, se lua non riesce a far crescere lo stack fino al numero di chiavi che vuoi eliminare (molto probabilmente a causa della mancanza di memoria), dovrai farlo con un ciclo for. Non consiglierei di farlo a meno che non sia necessario.
Eli,

1
Lua unpacktrasforma una tabella in un "elenco di variabili indipendenti" (altre lingue lo chiamano explode) ma il numero massimo non dipende dalla memoria del sistema; è riparato in lua attraverso la LUAI_MAXSTACKcostante. In Lua 5.1 e LuaJIT sono 8000 e in Lua 5.2 è 100000. L'opzione for loop è consigliata IMO.
kikito,

1
Vale la pena notare che lo script di lua è disponibile solo da Redis 2.6 in su
wallacer il

1
Qualsiasi soluzione basata su Lua viola la semantica EVALpoiché non specifica in anticipo i tasti su cui opererà. Dovrebbe funzionare su una singola istanza ma non aspettarti che funzioni con Redis Cluster.
Kevin Christopher Henry,

67

Dichiarazione di non responsabilità: la seguente soluzione non fornisce atomicità.

A partire dalla v2.8 vuoi davvero usare il comando SCAN invece di KEYS [1]. Il seguente script Bash mostra l'eliminazione delle chiavi per modello:

#!/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 è un comando pericoloso che può potenzialmente comportare un DoS. Quanto segue è un preventivo dalla sua pagina di documentazione:

Avvertenza: considerare KEYS come un comando che deve essere utilizzato solo in ambienti di produzione con estrema cura. Potrebbe rovinare le prestazioni quando viene eseguito su database di grandi dimensioni. Questo comando è destinato al debug e ad operazioni speciali, come la modifica del layout dello spazio chiavi. Non utilizzare KEYS nel normale codice dell'applicazione. Se stai cercando un modo per trovare le chiavi in ​​un sottoinsieme del tuo spazio delle chiavi, considera l'utilizzo di set.

AGGIORNAMENTO: una fodera per lo stesso effetto di base -

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

9
Tuttavia, evitare KEYS è sicuramente considerato la migliore pratica, quindi questa è un'ottima soluzione laddove sono possibili cancellazioni non atomiche.
fatal_error,

Questo ha funzionato per me; tuttavia, le mie chiavi si trovavano nel database 1. Quindi ho dovuto aggiungere -n 1ad ogni redis-cliinvocazione:redis-cli -n 1 --scan --pattern "*:foo:bar:*" | xargs -L 100 redis-cli -n 1 DEL
Rob Johansen

Nota che questo non funziona se le tue chiavi contengono caratteri speciali
mr1031011

Interessante e preziosa scoperta ... Mi chiedo se c'è un modo per citare cose per xargs ...
Itamar Haber

cosa fa -L 100 ??
Aparna,

41

Per coloro che avevano problemi ad analizzare altre risposte:

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

Sostituisci key:*:patterncon il tuo modello e inseriscilo in redis-clie sei a posto.

Credito da: http://redis.io/commands/del


37

Sto usando il comando seguente in redis 3.2.8

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

Puoi ottenere ulteriori informazioni relative alla ricerca del modello di chiavi da qui: - https://redis.io/commands/keys . Usa il tuo comodo modello in stile glob secondo i tuoi requisiti come *YOUR_KEY_PREFIX*o YOUR_KEY_PREFIX??o qualsiasi altro.

E se qualcuno di voi ha integrato la libreria Redis PHP rispetto alla seguente funzione vi aiuterà.

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);
            }
        }

Grazie :)


23

La soluzione di @ mcdizle non funziona, funziona solo per una voce.

Questo funziona per tutte le chiavi con lo stesso prefisso

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

Nota: è necessario sostituire "prefisso" con il prefisso chiave ...


2
usare lua è loooooot più veloce dell'uso di xargs, nell'ordine di 10 ^ 4.
deepak,

22

Puoi anche usare questo comando per cancellare le chiavi: -

Supponiamo che ci siano molti tipi di chiavi nel tuo redis come-

  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 " qui xyz è un sitename e queste chiavi sono correlate a prodotti e categorie di un sito di e-commerce e generate da FPC.

Se usi questo comando come di seguito-

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

O

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

Elimina tutte le chiavi come ' xyz_category_fpc ' ( elimina le chiavi 1, 2 e 3). Per cancellare altri tasti numerici 4, 5 e 6 usare ' xyz_product_fpc ' nel comando sopra.

Se desideri eliminare tutto in Redis , segui questi comandi-

Con redis-cli:

  1. FLUSHDB : rimuove i dati dal database CORRENTE della connessione.
  2. FLUSHALL - Rimuove i dati da TUTTI i database.

Ad esempio: - nella tua shell:

redis-cli flushall
redis-cli flushdb

3
Grazie, ma l'output delle tubazioni redis-cli delnon è atomico.
Alexander Gladysh,

13

Se hai spazio nel nome delle chiavi, puoi usarlo in bash:

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

10

La risposta di @ itamar è ottima, ma l'analisi della risposta non ha funzionato per me, esp. nel caso in cui non ci siano chiavi trovate in una data scansione. Una soluzione forse più semplice, direttamente dalla console:

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

Questo utilizza anche SCAN, che è preferibile a KEYS in produzione, ma non è atomico.


8

Ho appena avuto lo stesso problema. Ho archiviato i dati della sessione per un utente nel formato:

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

Pertanto, ogni voce era una coppia chiave-valore separata. Quando la sessione viene distrutta, volevo rimuovere tutti i dati della sessione eliminando le chiavi con il modello session:sessionid:*, ma redis non ha tale funzione.

Cosa ho fatto: memorizzare i dati della sessione in un hash . Mi basta creare un hash con l'id hash session:sessionide poi mi spingo key-x, key-y, key-zin questo hash (ordine non importa a me) e se non ho bisogno che hash più mi basta fare una DEL session:sessionide tutti i dati associati a tale hash id è andato. DELè atomico e l'accesso ai dati / la scrittura dei dati nell'hash è O (1).


Buona soluzione, ma i miei valori sono gli hash stessi. E Redis memorizza l'hash all'interno di un altro hash.
Alexander Gladysh,

3
Tuttavia, i campi all'interno di un hash mancano della funzionalità di scadenza, che a volte è davvero utile.
Evi Song,

per me questa è la risposta più pulita / più semplice finora
Sebastien H.

Un set non ha molto più senso?
Jack Tuck,


5

FYI.

  • usando solo bash e redis-cli
  • non usando keys(questo usa scan)
  • funziona bene in modalità cluster
  • non atomico

Forse devi solo modificare i caratteri maiuscoli.

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

Esegui al prompt di bash

$ ./clear-redis-key.sh key_head_pattern

5

Altre risposte potrebbero non funzionare se la chiave contiene caratteri speciali, Guide$CLASSMETADATA][1]ad esempio. Il confezionamento di ogni chiave tra virgolette garantirà che vengano eliminati correttamente:

redis-cli --scan --pattern sf_* | awk '{print $1}' | sed "s/^/'/;s/$/'/" | xargs redis-cli del

2
Questo script funziona perfettamente, testato con oltre 25000 chiavi.
Jordi

1
Puoi anche aggiungere le virgolette singole in awk usando questa espressione divertente `awk '{print"' "'"' "$ 1" '"'" '"}'`
Roberto Congiu,

3

Una versione che utilizza SCAN anziché KEYS (come raccomandato per i server di produzione) e --pipepiuttosto che xargs.

Preferisco pipe su xargs perché è più efficiente e funziona quando le tue chiavi contengono virgolette o altri caratteri speciali che la tua shell cerca e interpreta. La sostituzione regex in questo esempio racchiude la chiave tra virgolette doppie e sfugge a eventuali virgolette doppie all'interno.

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

Questa soluzione ha funzionato bene anche per me su tasti di circa 7m!
Danny,

2

Questa non è una risposta diretta alla domanda, ma poiché sono arrivato qui quando cercavo le mie risposte, lo condividerò qui.

Se hai decine o centinaia di milioni di chiavi che devi abbinare, le risposte fornite qui causeranno la mancata risposta di Redis per un periodo di tempo significativo (minuti?) E potenzialmente si arresta in modo anomalo a causa del consumo di memoria (assicurati, il salvataggio in background lo farà avviare nel bel mezzo dell'operazione).

L'approccio seguente è innegabilmente brutto, ma non ne ho trovato uno migliore. L'atomicità è fuori discussione qui, in questo caso l'obiettivo principale è mantenere Redis attivo e reattivo il 100% delle volte. Funzionerà perfettamente se hai tutte le tue chiavi in ​​uno dei database e non hai bisogno di abbinare alcun modello, ma non puoi usare http://redis.io/commands/FLUSHDB a causa della sua natura bloccante.

L'idea è semplice: scrivere uno script che viene eseguito in un ciclo e utilizza l'operazione O (1) come http://redis.io/commands/SCAN o http://redis.io/commands/RANDOMKEY per ottenere le chiavi, controlla se abbina il modello (se ne hai bisogno) e http://redis.io/commands/DEL loro uno per uno.

Se esiste un modo migliore per farlo, per favore fatemi sapere, aggiornerò la risposta.

Esempio di implementazione con randomkey in Ruby, come attività di rake, un sostituto non bloccante di qualcosa come 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

Si prega di utilizzare questo comando e provare:

redis-cli --raw keys "$PATTERN" | xargs redis-cli del

Non atomico e duplica altre risposte.
Matteo Leggi il

1

Ho provato la maggior parte dei metodi sopra menzionati ma non hanno funzionato per me, dopo alcune ricerche ho trovato questi punti:

  • se hai più di un db su redis devi determinare il database usando -n [number]
  • se hai alcune chiavi usate delma se ci sono migliaia o milioni di chiavi è meglio usarle unlinkperché unlink è non bloccante mentre del è bloccato, per maggiori informazioni visita questa pagina unlink vs del
  • anche keyssono come del e sta bloccando

quindi ho usato questo codice per cancellare le chiavi in ​​base al modello:

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

0

l'eliminazione di massa atomica del povero?

forse potresti impostarli tutti su EXPIREAT nello stesso secondo - come pochi minuti in futuro - e quindi aspettare fino a quel momento e vederli tutti "autodistruggersi" allo stesso tempo.

ma non sono sicuro di quanto sarebbe atomico.


0

Ad oggi, è possibile utilizzare un client redis ed eseguire prima SCAN (supporta la corrispondenza dei modelli) e quindi DEL ogni tasto singolarmente.

Tuttavia, c'è un problema su redis github ufficiale per creare un patter-matching-del qui , vai a mostrarlo un po 'd'amore se lo trovi utile!


-1

Sostengo tutte le risposte relative all'avere qualche strumento o eseguire l'espressione Lua.

Un'altra opzione da parte mia:

Nei nostri database di produzione e pre-produzione ci sono migliaia di chiavi. Di tanto in tanto è necessario eliminare alcune chiavi (con una maschera), modificarle con alcuni criteri, ecc. Naturalmente, non c'è modo di farlo manualmente dalla CLI, specialmente se si ha lo sharding (512 dbs logici in ogni fisico).

A questo scopo scrivo lo strumento client Java che fa tutto questo lavoro. In caso di cancellazione delle chiavi l'utilità può essere molto semplice, solo una classe lì:

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();
    }

}

-1

Sotto il comando ha funzionato per me.

redis-cli -h redis_host_url KEYS "*abcd*" | xargs redis-cli -h redis_host_url DEL

-3

Spring RedisTemplate stesso fornisce la funzionalità. RedissonClient nell'ultima versione ha deprecato la funzionalità "deleteByPattern".

Set<String> keys = redisTemplate.keys("geotag|*");
redisTemplate.delete(keys);

2
Ho aggiornato il codice di esempio di Redisson. Il tuo codice non ha un approccio atomico come fa Redisson. Ci sono nuove chiavi che potrebbero apparire tra keyse deleteinvocazioni di metodi.
Nikita Koksharov,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.