Cómo eliminar atómicamente las claves que coinciden con un patrón utilizando Redis

En mi Redis DB tengo un número de prefix: hash prefix: .

A veces quiero purgarlos atómicamente. ¿Cómo hago esto sin usar algún mecanismo de locking distribuido?

Comenzando con redis 2.6.0, puede ejecutar scripts lua, que se ejecutan atómicamente. Nunca he escrito uno, pero creo que se parecería a esto

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

Ver la documentación de EVAL .

Ejecutar en bash:

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

ACTUALIZAR

Esta bien, entendí. ¿Qué pasa con esto?: Almacena el prefijo incremental adicional actual y agrégalo a todas tus claves. Por ejemplo:

Usted tiene valores como este:

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

Cuando necesite purgar datos, cambie prefix_actuall primero (por ejemplo, configure prefix_prefix_actuall = 3), de modo que su aplicación escribirá nuevos datos en el prefijo de claves: 3: 1 y prefijo: 3: 2. A continuación, puede tomar con seguridad los valores anteriores del prefijo: 2: 1 y el prefijo: 2: 2 y purgar las claves antiguas.

Aquí hay una versión completamente funcional y atómica de una eliminación de comodín implementada en Lua. Funcionará mucho más rápido que la versión de xargs debido a una red mucho menos de ida y vuelta, y es completamente atómico, bloqueando cualquier otra solicitud contra redis hasta que finalice. Si desea eliminar las claves atómicamente en Redis 2.6.0 o superior, este es definitivamente el camino 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 es una versión de trabajo de la idea de @mcdizzle en su respuesta a esta pregunta. Crédito por la idea 100% va a él.

EDITAR: Por el comentario de Kikito a continuación, si tiene más claves para eliminar que memoria libre en su servidor Redis, se encontrará con el error “demasiados elementos para descomprimir” . En ese caso, hazlo:

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

Como sugirió Kikito.

Descargo de responsabilidad: la siguiente solución no proporciona atomicidad.

Comenzando con la v2.8, realmente desea utilizar el comando ESCANEAR en lugar de las LLAVES [1]. El siguiente script de Bash muestra la eliminación de claves por patrón:

 #!/bin/bash if [ $# -ne 3 ] then echo "Delete keys from Redis matching a pattern using SCAN & DEL" echo "Usage: $0   " 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 es un comando peligroso que puede potencialmente dar lugar a un DoS. La siguiente es una cita de su página de documentación:

Advertencia: considere las LLAVES como un comando que solo debe usarse en entornos de producción con extremo cuidado. Puede arruinar el rendimiento cuando se ejecuta en bases de datos grandes. Este comando está destinado a la depuración y operaciones especiales, como cambiar el diseño del espacio de claves. No use LLAVES en su código de aplicación habitual. Si está buscando una forma de encontrar claves en un subconjunto de su espacio de claves, considere usar conjuntos.

ACTUALIZACIÓN: un trazador de líneas para el mismo efecto básico –

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

Para aquellos que estaban teniendo problemas para analizar otras respuestas:

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

Reemplazar key:*:pattern con su propio patrón e ingresar esto en redis-cli y listo.

Credit lisco desde: http://redis.io/commands/del

Estoy usando el comando below en redis 3.2.8

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

Puede obtener más ayuda relacionada con la búsqueda de patrones de teclas desde aquí: – https://redis.io/commands/keys . Utilice su conveniente patrón de estilo glob según su requisito como *YOUR_KEY_PREFIX* o YOUR_KEY_PREFIX?? o cualquier otro.

Y si alguno de ustedes ha integrado la biblioteca Redis PHP, la siguiente función lo ayudará.

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

Gracias 🙂

La solución de @mcdizle no funciona, solo funciona para una entrada.

Este funciona para todas las teclas con el mismo prefijo

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

Nota: debe reemplazar ‘prefijo’ con su prefijo de tecla …

Si tiene espacio en el nombre de las teclas, puede usar esto en bash:

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

La respuesta de @ itamar es genial, pero el análisis de la respuesta no funcionaba para mí, especialmente. en el caso donde no hay llaves encontradas en un escaneo dado. Una solución posiblemente más simple, directamente desde la consola:

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

Esto también usa SCAN, que es preferible a KEYS en producción, pero no es atómico.

También puede usar este comando para eliminar las teclas: –

Supongamos que hay muchos tipos de claves en su 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 ‘ aquí xyz es un nombre de sitio, y estas claves están relacionadas con productos y categorías de un sitio de comercio electrónico y son generadas por FPC.

Si usa este comando de la siguiente manera:

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

O

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

Elimina todas las claves como ‘ xyz_category_fpc ‘ (eliminar 1, 2 y 3 teclas). Para borrar otras teclas numéricas 4, 5 y 6, use ‘ xyz_product_fpc ‘ en el comando anterior.

Si desea eliminar todo en Redis , siga estas Comandos-

Con redis-cli:

  1. FLUSHDB – Elimina datos de la base de datos CURRENT de su conexión.
  2. FLUSHALL – Elimina datos de TODAS las bases de datos.

Por ejemplo: en tu caparazón:

 redis-cli flushall redis-cli flushdb 

Acabo de tener el mismo problema. Almacenaba datos de sesión para un usuario en el formato:

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

Entonces, cada entrada era un par separado clave-valor. Cuando se destruye la sesión, quería eliminar todos los datos de la sesión eliminando claves con la session:sessionid:* patrón session:sessionid:* – pero redis no tiene esa función.

Lo que hice: almacenar los datos de la sesión dentro de un hash . Simplemente creé un hash con el hash id de la session:sessionid y luego presiono key-x , key-y , key-z en ese hash (el orden no me importaba) y si ya no necesito el hash, lo hago una DEL session:sessionid y todos los datos asociados con esa identificación hash desaparecieron. DEL es atómico y el acceso a datos / escritura de datos para el hash es O (1).

Creo que lo que podría ayudarte es el MULTI / EXEC / DISCARD . Si bien no es el 100% de las transacciones equivalentes , debería poder aislar las eliminaciones de otras actualizaciones.

FYI.

  • solo usando bash y redis-cli
  • no usa keys (esto usa scan )
  • funciona bien en modo de clúster
  • no atómico

Tal vez solo necesites modificar los caracteres capitales.

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 

Ejecutar en el indicador bash

 $ ./clear-redis-key.sh key_head_pattern 

Esta no es una respuesta directa a la pregunta, pero como llegué aquí cuando busco mis propias respuestas, lo compartiré aquí.

Si tiene decenas o cientos de millones de claves que tiene que coincidir, las respuestas dadas aquí causarán que Redis no responda durante un período de tiempo significativo (¿minutos?) Y potencialmente se bloquee debido al consumo de memoria (asegúrese de guardar el fondo). dar un puntapié en el medio de su operación).

El siguiente enfoque es innegablemente feo, pero no encontré uno mejor. La atomicidad está fuera de cuestión aquí, en este caso, el objective principal es mantener a Redis activo y receptivo el 100% del tiempo. Funcionará perfectamente si tiene todas sus claves en una de las bases de datos y no necesita hacer coincidir ningún patrón, pero no puede usar http://redis.io/commands/FLUSHDB debido a su naturaleza de locking.

Idea es simple: escriba un script que se ejecute en un bucle y use la operación O (1) como http://redis.io/commands/SCAN o http://redis.io/commands/RANDOMKEY para obtener claves, verifica si haga coincidir el patrón (si lo necesita) y http://redis.io/commands/DEL uno por uno.

Si hay una mejor manera de hacerlo, hágamelo saber, actualizaré la respuesta.

Implementación de ejemplo con la clave aleatoria en Ruby, como una tarea de rake, un sustituto no bloqueante 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 

Se implementa de forma sencilla mediante la funcionalidad “Eliminar sucursal” en FastoRedis , solo seleccione la twig que desea eliminar.

enter image description here

Una versión que usa SCAN en lugar de KEY (como se recomienda para los servidores de producción) y – --pipe lugar de xargs.

Prefiero pipe over xargs porque es más eficiente y funciona cuando las teclas contienen comillas u otros caracteres especiales con los que tu shell intenta e interpreta. La sustitución de expresiones regulares en este ejemplo envuelve la tecla entre comillas dobles, y escapa cualquier comilla doble 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 

la masa atómica del pobre, ¿borrar?

tal vez podría configurarlos todos para EXPULSAR en el mismo segundo, como unos minutos en el futuro, y luego esperar hasta ese momento y verlos a todos “autodestruirse” al mismo tiempo.

pero no estoy seguro de cuán atómico sería eso.

Apoyo todas las respuestas relacionadas con tener alguna herramienta o ejecutar la expresión Lua.

Una opción más de mi lado:

En nuestras bases de datos de producción y preproducción, existen miles de claves. De vez en cuando tenemos que eliminar algunas teclas (mediante alguna máscara), modificarlas según algunos criterios, etc. Por supuesto, no hay forma de hacerlo manualmente desde la CLI, especialmente si tiene fragmentación (512 dbps lógicos en cada elemento físico).

Para este propósito escribo una herramienta de cliente java que hace todo este trabajo. En caso de eliminación de teclas, la utilidad puede ser muy simple, solo una clase allí:

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

Spring RedisTemplate proporciona la funcionalidad. RedissonClient en la última versión ha dejado de usar la funcionalidad “deleteByPattern”.

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

redis-cli keys "*prefix*" funcionan para mí