Como excluir atomicamente chaves que correspondem a um padrão usando Redis

576

No meu Redis DB, tenho vários prefix:<numeric_id>hashes.

Às vezes eu quero limpar todos eles atomicamente. Como faço isso sem usar algum mecanismo de bloqueio distribuído?

Alexander Gladysh
fonte
Olá Steve, Há algum problema no meu site, eu o adicionei no meu outro blog mind-geek.net/nosql/redis/delete-keys-specific-expiry-time , Espero que ajude.
Gaurav Tewari
43
Esse é um cenário tão comum que eu gostaria que a equipe Redis considerasse adicionar um comando nativo para ele.
Todd Menier
Hoje em dia você pode fazer isso com Lua, veja abaixo.
Alexander Gladysh
3
@ToddMenier apenas sugerido, tem esta de volta raciocínio para por que isso nunca vai acontecer: github.com/antirez/redis/issues/2042
Ray
1
Muitas pessoas fizeram perguntas relacionadas a como lidar com um grande número de chaves, chaves com caracteres especiais, etc. Criei uma pergunta separada, pois estamos tendo esse problema agora e não acho que a resposta seja publicada. Aqui é a outra pergunta: stackoverflow.com/questions/32890648/...
jakejgordon

Respostas:

431

A partir do redis 2.6.0, você pode executar scripts lua, que são executados atomicamente. Eu nunca escrevi um, mas acho que seria algo como isto

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

Aviso : Como o documento Redis diz, por causa dos indicadores de desempenho, o keys comando não deve ser usado para operações regulares na produção, este comando é destinado à depuração e operações especiais. consulte Mais informação

Veja a documentação do EVAL .

mcdizzle
fonte
23
Nota importante: isso falhará se você tiver mais de duas mil chaves correspondentes ao prefixo.
Nathan Osman
93
Este está trabalhando para um grande número de chaves: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 19/08/14
181
Ai ... redis é muito usado como cache simples de chave / armazenamento. Esta parece del prefix:* ser uma operação fundamental: /
Ray
5
@Ray francamente, se você precisa que o recurso que você deve simplesmente dividir os dados por banco de dados numetic ou servidor e uso nivelado / flushdb
Marc Gravell
9
Sim, falha se nenhuma chave corresponder ao padrão. Para corrigir isso, adicionei uma chave padrão:EVAL "return redis.call('del', 'defaultKey', unpack(redis.call('keys', ARGV[1])))" 0 prefix:*
manuelmhtr 1/16/16
706

Executar no bash:

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

ATUALIZAR

OK eu entendi. E quanto a isso: armazene o prefixo incremental adicional atual e adicione-o a todas as suas chaves. Por exemplo:

Você tem valores como este:

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

Quando você precisar limpar os dados, altere o prefixo_actuall primeiro (por exemplo, defina prefix_prefix_actuall = 3), para que seu aplicativo grave novos dados nos prefixos de chaves: 3: 1 e prefixo: 3: 2. Em seguida, você pode pegar valores antigos com segurança do prefixo: 2: 1 e prefixo: 2: 2 e limpar as chaves antigas.

Casey
fonte
14
Desculpe, mas isso não é exclusão atômica. Alguém pode adicionar novas chaves entre KEYS e DEL. Eu não quero excluir aqueles.
Alexander Gladysh 24/10/10
36
As chaves que serão criadas após o comando KEYS não serão excluídas.
Casey
6
Eu só precisava limpar algumas teclas ruins, então a primeira resposta de Casey foi imediata, exceto que eu tive que mover as teclas para fora das aspas: redis-cli KEYS "prefix: *" | xargs redis-cli DEL
jslatts
19
A primeira resposta também me ajudou. Outra variante se as teclas redis contiverem aspas ou outros caracteres que bagunçam o xargs:redis-cli KEYS "prefix:*" | xargs --delim='\n' redis-cli DEL
overthink
18
Se você tem bancos de dados multible (keyspaces) então este é o truque: Vamos dizer que você precisa para chaves de exclusão em db3:redis-cli -n 3 KEYS "prefix:*" | xargs redis-cli -n 3 DEL
Christoffer
73

Aqui está uma versão completamente funcional e atômica de uma exclusão curinga implementada em Lua. Ele rodará muito mais rápido que a versão xargs devido a muito menos retorno e retorno da rede, e é completamente atômico, bloqueando outras solicitações contra o redis até que seja concluído. Se você deseja excluir atomicamente chaves no Redis 2.6.0 ou superior, este é definitivamente o caminho a seguir:

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

Esta é uma versão funcional da idéia de @ mcdizzle em sua resposta a esta pergunta. O crédito pela ideia 100% vai para ele.

EDIT: Pelo comentário de Kikito abaixo, se você tiver mais chaves para excluir do que memória livre no servidor Redis, encontrará o erro "muitos elementos para descompactar" . Nesse caso, faça:

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

Como Kikito sugeriu.

Eli
fonte
10
O código acima será exibido se você tiver um número significativo de chaves (o erro é "muitos elementos para descompactar"). Eu recomendo usar um laço na parte Lua:for _,k in ipairs(redis.call('keys', KEYS[1])) do redis.call('del', k) end
Kikito
@kikito, sim, se lua não puder aumentar a pilha para o número de chaves que você deseja excluir (provavelmente devido à falta de memória), será necessário fazê-lo com um loop for. Eu não recomendaria fazer isso, a menos que você precise.
Eli
1
Lua unpacktransforma uma tabela em uma "lista de variáveis ​​independentes" (outras línguas chamam isso explode), mas o número máximo não depende da memória do sistema; é fixado em lua através da LUAI_MAXSTACKconstante. No Lua 5.1 e LuaJIT é 8000 e no Lua 5.2 é 100000. A opção for loop é recomendada IMO.
Kikito
1
É importante notar que scripting lua só está disponível na Redis 2,6-se
wallacer
1
Qualquer solução baseada em Lua violará a semântica, EVALpois não especifica antecipadamente as chaves nas quais irá operar. Ele deve funcionar em uma única instância, mas não espere que funcione com o Redis Cluster.
Kevin Christopher Henry
67

Isenção de responsabilidade: a solução a seguir não fornece atomicidade.

A partir da v2.8, você realmente deseja usar o comando DIGITALIZAR em vez de KEYS [1]. O script Bash a seguir demonstra a exclusão de chaves por padrão:

#!/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 é um comando perigoso que pode resultar potencialmente em um DoS. A seguir, uma citação de sua página de documentação:

Aviso: considere KEYS como um comando que deve ser usado apenas em ambientes de produção com extremo cuidado. Isso pode prejudicar o desempenho quando executado em bancos de dados grandes. Este comando destina-se à depuração e operações especiais, como alterar o layout do espaço da chave. Não use KEYS no seu código de aplicativo normal. Se você estiver procurando uma maneira de encontrar chaves em um subconjunto do seu espaço de chaves, considere usar conjuntos.

UPDATE: um liner para o mesmo efeito básico -

$ redis-cli --scan --pattern "*:foo:bar:*" | xargs -L 100 redis-cli DEL
Itamar Haber
fonte
9
No entanto, evitar KEYS é definitivamente considerado uma prática recomendada, portanto, essa é uma ótima solução sempre que possíveis exclusões não atômicas.
fatal_error
Isso funcionou para mim; no entanto, minhas chaves passou a ser na base de dados 1. Então, eu tive que adicionar -n 1a cada redis-cliinvocação:redis-cli -n 1 --scan --pattern "*:foo:bar:*" | xargs -L 100 redis-cli -n 1 DEL
Rob Johansen
Observe que isso não funciona se suas chaves contiverem caracteres especiais
mr1031011 14/11
Achado interessante e valioso ... Gostaria de saber se há uma maneira de citar as coisas para xargs ...
Itamar Haber
o que -L 100 faz?
Aparna
41

Para aqueles que estavam tendo problemas para analisar outras respostas:

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

Substitua key:*:patternpor seu próprio padrão e insira isso redis-clie você estará pronto.

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

randomor
fonte
37

Estou usando o comando abaixo no redis 3.2.8

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

Você pode obter mais ajuda relacionada à pesquisa de padrões de chaves aqui: - https://redis.io/commands/keys . Use o seu padrão de estilo glob conveniente como por sua exigência como *YOUR_KEY_PREFIX*ou YOUR_KEY_PREFIX??ou qualquer outro.

E se algum de vocês tiver integrado a biblioteca Redis PHP, a função abaixo o ajudará.

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

Obrigado :)

Yashrajsinh Jadeja
fonte
23

A solução do @ mcdizle não está funcionando, funciona apenas para uma entrada.

Este funciona para todas as chaves com o mesmo prefixo

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

Nota: Você deve substituir 'prefixo' pelo seu prefixo de chave ...

efaruk
fonte
2
usar lua é muito mais rápido que usar xargs, na ordem de 10 ^ 4.
Deepak
22

Você também pode usar este comando para excluir as chaves: -

Suponha que haja muitos tipos de chaves nos seus redis, como

  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 ' aqui xyz é um nome de site e essas chaves estão relacionadas a produtos e categorias de um site de comércio eletrônico e geradas pelo FPC.

Se você usar este comando como abaixo

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

OU

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

Exclui todas as chaves como ' xyz_category_fpc ' (exclua as teclas 1, 2 e 3). Para excluir outras teclas numéricas 4, 5 e 6, use ' xyz_product_fpc ' no comando acima.

Se você deseja excluir tudo no Redis , siga estes comandos

Com redis-cli:

  1. FLUSHDB - Remove os dados do banco de dados ATUAL da sua conexão.
  2. FLUSHALL - Remove dados de TODOS os bancos de dados.

Por exemplo: - no seu shell:

redis-cli flushall
redis-cli flushdb
Vishal Thakur
fonte
3
Obrigado, mas a saída da tubulação para redis-cli delnão é atômica.
Alexander Gladysh 12/01
13

Se você tiver espaço no nome das chaves, poderá usá-lo no bash:

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

A resposta de @ itamar é ótima, mas a análise da resposta não estava funcionando para mim, esp. no caso em que não há chaves encontradas em uma determinada varredura. Uma solução possivelmente mais simples, diretamente do console:

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

Isso também usa SCAN, que é preferível a KEYS na produção, mas não é atômico.

Guitan
fonte
8

Eu apenas tive o mesmo problema. Eu armazenei os dados da sessão para um usuário no formato:

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

Portanto, cada entrada era um par de valores-chave separado. Quando a sessão é destruída, eu queria remover todos os dados da sessão excluindo chaves com o padrão session:sessionid:*- mas o redis não possui essa função.

O que eu fiz: armazene os dados da sessão em um hash . Eu só criar um hash com a ID de hash session:sessionide então eu empurrar key-x, key-y, key-zem que de hash (ordem não importa para mim) e se eu não preciso esse hash mais Eu só faço um DEL session:sessionide todos os dados associados a esse ID de hash é ido. DELé atômico e o acesso aos dados / gravação de dados no hash é O (1).

Máx.
fonte
Boa solução, mas meus valores são eles mesmos. E o Redis armazena o hash dentro de outro hash.
Alexander Gladysh
3
No entanto, os campos em um hash não possuem a funcionalidade expirar, que às vezes é realmente útil.
Evi Song 10/10
para mim esta é a resposta mais limpo / mais simples até agora
Sebastien H.
Um conjunto não faz muito mais sentido?
Jack Tuck
5

Eu acho que o que pode ajudá-lo é o MULTI / EXEC / DISCARD . Embora não seja 100% equivalente a transações , você poderá isolar as exclusões de outras atualizações.

alexpopescu
fonte
4
Mas não consigo descobrir como usá-los aqui. DEL é atômico por si só (ou assim eu acho). E não consigo obter valores de KEYS até executar EXEC; portanto, não posso usar KEYS e DEL no mesmo MULTI.
Alexander Gladysh 24/10/10
5

PARA SUA INFORMAÇÃO.

  • usando apenas bash e redis-cli
  • não usando keys(isso usa scan)
  • funciona bem no modo de cluster
  • não atômico

Talvez você precise modificar apenas caracteres maiúsculos.

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

Executar no prompt do bash

$ ./clear-redis-key.sh key_head_pattern
plhn
fonte
5

Outras respostas podem não funcionar se sua chave contiver caracteres especiais - Guide$CLASSMETADATA][1]por exemplo. A quebra de cada chave entre aspas garantirá que elas sejam excluídas corretamente:

redis-cli --scan --pattern sf_* | awk '{print $1}' | sed "s/^/'/;s/$/'/" | xargs redis-cli del
Quentin S.
fonte
2
Esse script funciona perfeitamente, testado com mais de 25000 chaves.
Jordi
1
Você também pode adicionar aspas simples no awk usando esta expressão engraçada `awk '{print"' "'"' "$ 1" '"'" '}}' `
Roberto Congiu
3

Uma versão que usa SCAN em vez de KEYS (como recomendado para servidores de produção) e em --pipevez de xargs.

Prefiro canalizar sobre xargs porque é mais eficiente e funciona quando suas chaves contêm aspas ou outros caracteres especiais que seu shell tenta e interpreta. A substituição de expressão regular neste exemplo envolve a chave entre aspas duplas e escapa as aspas duplas dentro.

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
tekumara
fonte
Esta solução funcionou bem para mim, mesmo em teclas de aproximadamente 7 m!
Danny
2

Esta não é uma resposta direta à pergunta, mas como cheguei aqui ao procurar minhas próprias respostas, vou compartilhar isso aqui.

Se você tiver dezenas ou centenas de milhões de chaves com as quais corresponder, as respostas fornecidas aqui farão com que os Redis não respondam por um período significativo de tempo (minutos?) E potencialmente falhem devido ao consumo de memória (certifique-se de que o salvamento em segundo plano será chute no meio de sua operação).

A abordagem a seguir é inegavelmente feia, mas não encontrei uma melhor. A atomicidade está fora de questão aqui, neste caso, o principal objetivo é manter o Redis ativo e responsivo 100% do tempo. Funcionará perfeitamente se você tiver todas as suas chaves em um dos bancos de dados e não precisar corresponder a nenhum padrão, mas não puder usar http://redis.io/commands/FLUSHDB por causa de sua natureza bloqueadora.

A idéia é simples: escreva um script que execute em loop e use a operação O (1) como http://redis.io/commands/SCAN ou http://redis.io/commands/RANDOMKEY para obter chaves, verifique se elas corresponda ao padrão (se necessário) e http://redis.io/commands/DEL, um por um.

Se houver uma maneira melhor de fazer isso, entre em contato, atualizarei a resposta.

Exemplo de implementação com randomkey no Ruby, como uma tarefa rake, um substituto não-bloqueador de algo como 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
Spajus
fonte
2

É simples implementado através da funcionalidade "Remover ramo" no FastoRedis , basta selecionar o ramo que você deseja remover.

insira a descrição da imagem aqui

Topilski Alexandr
fonte
Isso é feito atomicamente? E como isso ajuda no código ?
Mateus Leia
2

Por favor, use este comando e tente:

redis-cli --raw keys "$PATTERN" | xargs redis-cli del
Suf_Malek
fonte
Não atômico, e duplica outras respostas.
Mateus Leia
1

Eu tentei a maioria dos métodos mencionados acima, mas eles não funcionaram para mim, depois de algumas pesquisas, encontrei estes pontos:

  • se você tiver mais de um banco de dados no redis, determine o banco de dados usando -n [number]
  • se você usa algumas chaves, delmas se existem milhares ou milhões de chaves, é melhor usar unlinkporque o desvincular não está bloqueando enquanto o del está bloqueando, para obter mais informações, visite esta página unlink vs del
  • também keyssão como del e está bloqueando

então usei esse código para excluir chaves por padrão:

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

apagamento atômico em massa do pobre homem?

talvez você possa configurá-los para EXPIREAT no mesmo segundo - como alguns minutos no futuro - e esperar até esse momento e vê-los todos "se autodestruir" ao mesmo tempo.

mas não tenho muita certeza de quão atômica isso seria.

Chris
fonte
0

Agora, você pode usar um cliente redis e executar a primeira DIGITALIZAÇÃO (suporta a correspondência de padrões) e, em seguida, DEL cada tecla individualmente.

No entanto, existe um problema no redis github oficial para criar um delineador de correspondência de patter aqui , mostre-lhe um pouco de amor se achar útil!

Asalle
fonte
-1

Eu apoio todas as respostas relacionadas a ter alguma ferramenta ou executar a expressão Lua.

Mais uma opção do meu lado:

Nos nossos bancos de dados de produção e pré-produção, existem milhares de chaves. Ocasionalmente, precisamos excluir algumas chaves (por alguma máscara), modificar por alguns critérios etc. É claro que não há como fazê-lo manualmente a partir da CLI, principalmente com sharding (512 dbs lógicos em cada físico).

Para esse propósito, escrevo a ferramenta cliente java que faz todo esse trabalho. No caso de exclusão de chaves, o utilitário pode ser muito simples, apenas uma 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();
    }

}
Denys
fonte
-1

O comando abaixo funcionou para mim.

redis-cli -h redis_host_url KEYS "*abcd*" | xargs redis-cli -h redis_host_url DEL
Sumit Saurabh
fonte
-3

O Spring RedisTemplate propriamente dito fornece a funcionalidade. O RedissonClient na versão mais recente descontinuou a funcionalidade "deleteByPattern".

Set<String> keys = redisTemplate.keys("geotag|*");
redisTemplate.delete(keys);
Arijeet Saha
fonte
2
Eu atualizei o código de exemplo Redisson. Seu código não está em uma abordagem atômica como Redisson. Existem novas chaves que podem aparecer entre os métodos keyse deleteinvocações.
Nikita Koksharov