Как атомарно удалить ключи, соответствующие шаблону, используя Redis

В моей БД Redis у меня есть ряд prefix:<numeric_id> хэши.

Иногда я хочу очистить их все атомарно. Как мне сделать это без использования какого-либо механизма распределенной блокировки?

36 ответов

Решение

Начиная с redis 2.6.0, вы можете запускать сценарии lua, которые выполняются атомарно. Я никогда не писал, но я думаю, что это будет выглядеть примерно так

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

Смотрите документацию EVAL.

Выполнить в Bash:

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

ОБНОВИТЬ

Хорошо, я понял. Как насчет этого: сохранить текущий дополнительный инкрементный префикс и добавить его ко всем вашим ключам. Например:

У вас есть такие значения:

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

Когда вам нужно очистить данные, вы сначала изменяете prefix_actuall (например, установите prefix_prefix_actuall = 3), чтобы ваше приложение записывало новые данные в префикс префикса ключей:3:1 и префикс 3:2. Тогда вы можете безопасно взять старые значения из префикса:2:1 и префикса:2:2 и очистить старые ключи.

Вот полностью рабочая и атомарная версия удаления с подстановочными знаками, реализованная в Lua. Он будет работать намного быстрее, чем версия xargs, благодаря гораздо меньшей скорости передачи данных по сети и полностью атомарен, блокируя любые другие запросы от redis до его завершения. Если вы хотите атомарно удалить ключи в Redis 2.6.0 или более поздней версии, это, безусловно, путь:

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

Это рабочая версия идеи @mcdizzle в ответе на этот вопрос. Кредит на идею 100% достается ему.

РЕДАКТИРОВАТЬ: Согласно комментарию Kikito ниже, если у вас есть больше ключей для удаления, чем свободной памяти на вашем сервере Redis, вы столкнетесь с ошибкой "слишком много элементов для распаковки". В этом случае сделайте:

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

Как предложил Кикито.

Отказ от ответственности: следующее решение не обеспечивает атомарность.

Начиная с v2.8 вы действительно хотите использовать команду SCAN вместо KEYS[1]. Следующий скрипт Bash демонстрирует удаление ключей по шаблону:

#!/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 - опасная команда, которая может привести к DoS. Ниже приводится цитата со страницы документации:

Предупреждение: рассматривайте KEYS как команду, которую следует использовать только в производственных средах с особой осторожностью. Это может привести к снижению производительности при выполнении в больших базах данных. Эта команда предназначена для отладки и специальных операций, таких как изменение раскладки клавиатуры. Не используйте клавиши в вашем обычном коде приложения. Если вы ищете способ найти ключи в подмножестве вашего пространства ключей, подумайте об использовании наборов.

ОБНОВЛЕНИЕ: один вкладыш для того же базового эффекта -

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

Для тех, у кого были проблемы с разбором других ответов:

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

замещать key:*:pattern с вашим собственным шаблоном и введите это в redis-cli и ты в порядке.

Кредит Лиско от: http://redis.io/commands/del

Я использую команду ниже в Redis 3.2.8

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

Вы можете получить дополнительную помощь, связанную с поиском по шаблону ключей, здесь: - https://redis.io/commands/keys. Используйте свой удобный шаблон стиля шарика согласно вашему требованию как *YOUR_KEY_PREFIX* или же YOUR_KEY_PREFIX?? или любой другой.

И если кто-то из вас интегрировал библиотеку Redis PHP, то вам поможет нижеследующая функция.

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

Спасибо:)

Вы также можете использовать эту команду для удаления ключей:

Предположим, в вашем Redis есть много типов ключей, таких как

  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'

Например, xyz_category_fpc здесь xyz - это собственное имя, и эти ключи относятся к продуктам и категориям сайта электронной коммерции и генерируются FPC.

Если вы используете эту команду, как показано ниже:

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

ИЛИ ЖЕ

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

Он удаляет все ключи, такие как " xyz_category_fpc " (удалить ключи 1, 2 и 3). Для удаления других 4, 5 и 6 цифровых клавиш используйте команду " xyz_product_fpc " в приведенной выше команде.

Если вы хотите удалить все в Redis, выполните следующие команды:

С редис-кли:

  1. FLUSHDB - удаляет данные из базы данных CURRENT вашего соединения.
  2. FLUSHALL - Удаляет данные из ВСЕХ баз данных.

Например:- в вашей оболочке:

redis-cli flushall
redis-cli flushdb

Решение @mcdizle не работает, оно работает только для одной записи.

Этот работает для всех ключей с одинаковым префиксом

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

Примечание: вы должны заменить префикс вашим префиксом ключа...

Мне это удалось с помощью простейшего варианта команды EVAL:

      EVAL "return redis.call('del', unpack(redis.call('keys', my_pattern_here*)))" 0

где я заменил my_pattern_here с моей ценностью.

Если у вас есть пробел в имени ключей, вы можете использовать это в bash:

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

// TODO

Вы думаете, что эта команда не имеет смысла, но иногда команда Redis, например DEL не работает правильно и приходит на помощь этому

redis-cli KEYS "*" | xargs -i redis-cli EXPIRE {} 1это лайфхак

Другие ответы могут не работать, если ваш ключ содержит специальные символы - Guide$CLASSMETADATA][1]например. Заключение каждого ключа в кавычки обеспечит их правильное удаление:

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

Ответ @itamar великолепен, но синтаксический анализ ответа не помог мне, особенно. в случае, когда в данном сканировании не найдено ключей. Возможно более простое решение, прямо из консоли:

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

При этом также используется SCAN, который предпочтительнее, чем KEYS в производстве, но не атомарный.

У меня просто была такая же проблема. Я сохранил данные сеанса для пользователя в формате:

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

Итак, каждая запись была отдельной парой ключ-значение. Когда сеанс уничтожен, я хотел удалить все данные сеанса, удалив ключи с шаблоном session:sessionid:* - но у redis такой функции нет.

Что я сделал: сохранить данные сеанса в хэше. Я просто создаю хэш с идентификатором хеша session:sessionid а потом я нажимаю key-x, key-y, key-z в этом хеше (порядок не имеет значения для меня), и если мне больше не нужен этот хэш, я просто делаю DEL session:sessionid и все данные, связанные с этим хеш-идентификатором, исчезли. DEL является атомарным, и доступ к данным / запись данных в хэш равен O(1).

это самый простой способ, который приходит на ум, без использования магии xargs

чистый бред!

      redis-cli DEL $(redis-cli KEYS *pattern*)

Добавляем к этому ответу:

Чтобы найти первые 1000 ключей:

      EVAL "return redis.call('scan', 0, 'COUNT', 1000, 'MATCH', ARGV[1])" 0 find_me_*

Чтобы удалить их:

      EVAL "return redis.call('del', unpack(redis.call('SCAN', 0, 'COUNT', 1000, 'MATCH', ARGV[1])[2]))" 0 delete_me_*

Версия, использующая SCAN, а не KEYS (как рекомендуется для производственных серверов) и --pipe а не xargs.

Я предпочитаю pipe, а не xargs, потому что он более эффективен и работает, когда ваши ключи содержат кавычки или другие специальные символы, которые ваша оболочка пытается интерпретировать. Подстановка регулярного выражения в этом примере оборачивает ключ в двойные кавычки и экранирует любые двойные кавычки внутри.

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

FYI.

  • только используя Bash и redis-cli
  • не используется keys (это использует scan)
  • хорошо работает в кластерном режиме
  • не атомный

Может быть, вам нужно только изменить заглавные буквы.

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

Запустите по приглашению Bash

$ ./clear-redis-key.sh key_head_pattern

Пожалуйста, используйте эту команду и попробуйте:

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

Я думаю, что вам может помочь MULTI / EXEC / DISCARD. Хотя транзакции не эквивалентны 100%, вы должны быть в состоянии изолировать удаления от других обновлений.

Это не прямой ответ на вопрос, но, поскольку я попал сюда при поиске своих собственных ответов, я поделюсь этим здесь.

Если у вас есть десятки или сотни миллионов ключей, которые вы должны сопоставить, ответы, приведенные здесь, приведут к тому, что Redis не будет реагировать в течение значительного времени (минут?), И может привести к сбою из-за потребления памяти (будьте уверены, фоновое сохранение будет пнуть в середине вашей операции).

Следующий подход, несомненно, некрасив, но лучшего я не нашел. Атомность здесь не обсуждается, в этом случае главная цель - поддерживать Redis и реагировать на него 100% времени. Он будет отлично работать, если у вас есть все ключи в одной из баз данных, и вам не нужно сопоставлять какой-либо шаблон, но вы не можете использовать http://redis.io/commands/FLUSHDB из-за его природы блокировки.

Идея проста: написать скрипт, который запускается в цикле и использует операцию O(1), например http://redis.io/commands/SCAN или http://redis.io/commands/RANDOMKEY чтобы получить ключи, проверяет, сопоставьте шаблон (если он вам нужен) и http://redis.io/commands/DEL.

Если есть лучший способ сделать это, пожалуйста, дайте мне знать, я обновлю ответ.

Пример реализации с помощью randomkey в Ruby, как задача rake, неблокирующая замена чего-то вроде 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

Если мы хотим убедиться в работе атома, мы можем попробовать написать Lua-скрипт.

Если ваша версия Redis поддерживаетSCANичто выше, чем4.0.0, я предпочитаю использоватьиUNLINKвместо и в производственной среде, потому чтоKeyиDELкоманды могут блокировать

их можно использовать в продакшене без недостатков таких команд, как KEYS или SMEMBERS, которые могут блокировать сервер на долгое время (даже несколько секунд) при вызове для больших коллекций ключей или элементов.

      EVAL "local cursor = 0 repeat local result = redis.call('SCAN', cursor, 'MATCH', ARGV[1])    for _,key in ipairs(result[2]) do  redis.call('UNLINK', key)   end  cursor = tonumber(result[1]) until cursor == 0 " 0 prefix:*

Мы можем изменитьprefix:*как мы хотим.

Я попробовал большинство методов, упомянутых выше, но они не сработали для меня, после некоторых поисков я нашел следующие пункты:

  • если у вас есть более одной базы данных на Redis, вы должны определить базу данных, используя -n [number]
  • если у вас есть несколько ключей del но если есть тысячи или миллионы ключей, лучше использовать unlink поскольку unlink не блокирует, а del блокирует, для получения дополнительной информации посетите эту страницу unlink против del
  • также keys как дель и блокирует

поэтому я использовал этот код для удаления ключей по шаблону:

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

Команда ниже работала для меня.

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

Если в именах ключей есть пробелы, это будет работать с MacOS.

      redis-cli --scan --pattern "myprefix:*" | tr \\n \\0 | xargs -0 redis-cli unlink

Это сработало для меня:

      redis-cli keys "stats.*" | cut -d ' ' -f2 | xargs -d '\n' redis-cli DEL

Я знаю, что на этот вопрос уже дан ответ и он работает. Однако нередко приходится работать с докеризованным Redis. Затем команда:

      docker exec redis-docker-container-name redis-cli --scan --pattern "*" | while read key; do   docker exec redis-docker-container-name redis-cli del "$key"; done

Это просто реализуется через функцию "Удалить ветку" в FastoRedis, просто выберите ветку, которую вы хотите удалить.

Если вы не против установить графический интерфейс, Redis предоставляет приложение под названием RedisInsight , которое поддерживает массовые действия, поэтому вы можете фильтровать ключи с помощью подстановочного знака и удалять их все.

А теперь вы можете использовать клиент Redis и выполнить сначала сканирование (поддерживает сопоставление с образцом), а затем удалить каждый ключ по отдельности.

Тем не менее, существует проблема на официальном Redis GitHub создать скороговорка-сопрягая-дель здесь, идет показать ему некоторую любовь, если вы найдете его полезным!

Другие вопросы по тегам