Як атомно видалити ключі, що відповідають шаблону за допомогою Redis


575

У моїй DB Redis у мене є ряд prefix:<numeric_id>хешей.

Іноді хочеться очистити їх усі атомно. Як це зробити, не використовуючи механізму розподіленого блокування?


Привіт Стіве, є проблема з моїм веб-сайтом, я додав її до свого іншого блогу mind-geek.net/nosql/redis/delete-keys-specific-expiry-time , сподіваюся, це допоможе.
Gaurav Tewari

43
Це такий розповсюджений сценарій, що я хотів би, щоб команда Redis розглядала можливість додавання для нього рідної команди.
Тодд Меньє

Сьогодні ви можете просто зробити це з Lua, дивіться нижче.
Олександр Гладиш

3
@ToddMenier Щойно запропонував, поверніть це міркування, чому це ніколи не відбудеться: github.com/antirez/redis/isissue/2042
Рей

1
Багато людей задають відповідні запитання про те, як обробити велику кількість клавіш, клавіш із спеціальними символами тощо. Я створив окреме запитання, оскільки у нас зараз ця проблема, і я не думаю, що відповідь розміщена на це питання. Ось інше питання: stackoverflow.com/questions/32890648 / ...
jakejgordon

Відповіді:


431

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

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

Попередження : Як йдеться в документі Redis , через те, що питання продуктивності keys команду не слід використовувати для регулярних операцій у виробництві, ця команда призначена для налагодження та спеціальних операцій. читати більше

Дивіться документацію EVAL .


23
Важлива примітка: це не вдається, якщо у вас є більше декількох тисяч ключів, що відповідають префіксу.
Натан Осман

93
Цей працює для великої кількості клавіш: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
Ой ... redis дуже часто використовується як простий кеш клавіш / зберігання. Це, мабуть, del prefix:* має бути фундаментальною операцією: /
Рей

5
@ Відверто кажучи, якщо вам потрібна ця функція, вам слід просто розділити дані за допомогою цифрової бази даних або сервера і використовувати flush / flushdb
Marc Gravell

9
Так, це не вдається, якщо жодна клавіша не відповідає шаблону. Щоб виправити, що я додав ключ за замовчуванням:EVAL "return redis.call('del', 'defaultKey', unpack(redis.call('keys', ARGV[1])))" 0 prefix:*
manuelmhtr

706

Виконати в 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 та очистити старі ключі.


14
Вибачте, але це не атомне видалення. Хтось може додати нові клавіші між KEYS та DEL. Я не хочу їх видаляти.
Олександр Гладиш

36
Ключі, які будуть створені після того, як команда KEYS не буде видалена.
Кейсі

6
Мені просто потрібно було очистити деякі погані ключі, тому перша відповідь Кейсі виявилася на місці, за винятком того, що мені довелося переміщувати ключі поза цитатами: redis-cli KEYS "префікс: *" | xargs redis-cli DEL
jslatts

19
Перша відповідь також допомогла мені. Інший варіант, якщо ваші клавіші redis містять лапки або інші символи, які псують xargs:redis-cli KEYS "prefix:*" | xargs --delim='\n' redis-cli DEL
переосмислити

18
Якщо у вас є багатозахисні бази даних (простори ключів), то в цьому і полягає фокус: Скажімо, вам потрібно видалити ключі в db3:redis-cli -n 3 KEYS "prefix:*" | xargs redis-cli -n 3 DEL
Christoffer

73

Ось повністю працююча та атомна версія видалення підстановок, реалізована в Луа. Вона запуститься набагато швидше, ніж версія 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% йде йому.

EDIT: За коментарем Кікіто нижче, якщо у вас є більше клавіш для видалення, ніж вільна пам'ять на вашому сервері Redis, ви зіткнетеся з помилкою "занадто багато елементів для розпакування" . У цьому випадку зробіть:

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

Як запропонував Кікіто.


10
Код, наведений вище, буде заповнено, якщо у вас є значна кількість клавіш (помилка - "занадто багато елементів для розпакування"). Я рекомендую використовувати петлю на частині Луа:for _,k in ipairs(redis.call('keys', KEYS[1])) do redis.call('del', k) end
kikito

@kikito, так, якщо lua не зможе збільшити стек до кількості клавіш, які потрібно видалити (швидше за все, через брак пам'яті), вам потрібно буде зробити це за допомогою циклу for. Я б не рекомендував робити це, якщо не потрібно.
Елі

1
Lua unpackперетворює таблицю у "список незалежних змінних" (інші мови називають це explode), але максимальне число не залежить від пам'яті системи; він фіксується в луа через LUAI_MAXSTACKпостійну. У Lua 5.1 та LuaJIT це 8000, а в Lua 5.2 - 100000. Опція для циклу рекомендується IMO.
кікіто

1
Варто відзначити, що сценарії lua доступні лише у Redis 2.6 up
wallacer

1
Будь-яке рішення на основі Lua буде порушувати семантику, EVALоскільки не зазначає заздалегідь ключі, над якими воно буде працювати. Він повинен працювати в одному екземплярі, але не сподівайтеся, що він буде працювати з класом Redis.
Кевін Крістофер Генрі

66

Відмова: наступне рішення не забезпечує атомності.

Починаючи з 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 як команду, яку слід використовувати лише у виробничих середовищах з особливою обережністю. Це може призвести до погіршення продуктивності, коли воно виконується на великих базах даних. Ця команда призначена для налагодження та спеціальних операцій, таких як зміна макета простору ключів. Не використовуйте KEYS у звичайному коді програми. Якщо ви шукаєте спосіб знайти ключі в підмножині простору клавіш, подумайте про використання наборів.

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

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

9
Тим не менш, уникати KEYS, безумовно, вважається найкращою практикою, тому це чудове рішення там, де не атомні делети є можливими.
fatal_error

Це працювало для мене; однак, мої ключі виявилися в базі даних 1. Тож мені довелося додати -n 1до кожного redis-cliвиклику:redis-cli -n 1 --scan --pattern "*:foo:bar:*" | xargs -L 100 redis-cli -n 1 DEL
Роб Йохансен

Зауважте, що це не працює, якщо ваші клавіші містять спеціальні символи
mr1031011

Цікава та цінна знахідка ... Цікаво, чи є спосіб процитувати речі для xargs ...
Itamar Haber

що робить -L 100 робити ??
Апарна

41

Для тих, хто зіткнувся з аналізом інших відповідей:

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


37

Я використовую команду нижче в редакції 3.2.8

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

Додаткову допомогу щодо пошуку шаблонів ключів можна отримати тут: - https://redis.io/commands/keys . Використовуйте зручний шаблон Glob стилі згідно вашу вимогу , як *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);
            }
        }

Дякую :)


23

@ mcdizle не працює, він працює лише для одного запису.

Цей працює для всіх клавіш з однаковим префіксом

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

Примітка. Ви повинні замінити "префікс" вашим ключовим префіксом ...


2
використання lua швидше, ніж використання xargs, в порядку 10 ^ 4.
деепак

22

Ви також можете скористатися цією командою для видалення ключів: -

Припустимо, у вашому 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 , виконайте ці команди,

З Redis-cli:

  1. FLUSHDB - видаляє дані з поточної бази даних вашого з'єднання.
  2. FLUSHALL - видаляє дані з ВСІХ баз даних.

Наприклад: - у вашій оболонці:

redis-cli flushall
redis-cli flushdb

3
Дякую, але вихід трубопроводів redis-cli delне атомний.
Олександр Гладиш

13

Якщо у назві клавіш є місце, ви можете використовувати це в bash:

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

10

@ відповідь itamar чудова, але аналіз відповіді не спрацював для мене, esp. у випадку, коли в даному скануванні не знайдено жодних ключів. Можливо, більш просте рішення безпосередньо з консолі:

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

Для цього також використовується SCAN, який є кращим для KEYS у виробництві, але не є атомним.


8

У мене просто була така ж проблема. Я зберігав дані сеансу для користувача у форматі:

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).


Гарне рішення, але мої цінності - це хеши. І Redis зберігає хеш всередині іншого хешу.
Олександр Гладиш

3
Однак поля в хеші не мають функції функцій закінчення терміну дії, що іноді дуже корисно.
Пісня Еві

для мене це найчистіша / найпростіша відповідь на сьогодні
Себастьян Х.

Хіба набір не має сенсу?
Джек Так

5

Я думаю, що може допомогти вам МУЛЬТИ / ВИДАЛЕННЯ / ВІДКРИТТЯ . Хоча це не 100% еквівалент транзакцій , ви можете бути в змозі ізолювати делетів від інших оновлень.


4
Але я не можу зрозуміти, як їх тут використовувати. DEL - атомний сам по собі (або так я думаю). І я не можу отримати значення від KEYS до тих пір, поки не стану EXEC, тому я не можу використовувати KEYS і DEL в одній MULTI.
Олександр Гладиш

5

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

Запустіть у баш-підказці

$ ./clear-redis-key.sh key_head_pattern

5

Інші відповіді можуть не працювати, якщо ваш ключ містить спеціальні символи - Guide$CLASSMETADATA][1]наприклад. Обгортання кожної клавіші в лапки забезпечить їх належне видалення:

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

2
Цей сценарій працює ідеально, випробуваний з більш ніж 25000 клавішами.
Джорді

1
Ви також можете додати одиночні лапки в awk, використовуючи цей кумедний вираз `awk '{print"' "" "" $ 1 "'" "" "}` `
Роберто Конгі

3

Версія, що використовує SCAN, а не KEYS (як рекомендується для виробничих серверів), --pipeа не xargs.

Я віддаю перевагу передачі даних над 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

Це рішення добре працювало для мене навіть на клавішах приблизно 7 м!
Денні

2

Це не пряма відповідь на питання, але оскільки я потрапив сюди під час пошуку власних відповідей, я поділюсь цим тут.

Якщо у вас є десятки або сотні мільйонів ключів, з якими вам доведеться відповідати, відповіді, наведені тут, призведуть до того, що 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, як завдання граблі, що не блокує заміну чогось типу 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

Будь ласка, використовуйте цю команду і спробуйте:

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

Не атомний, а дублює інші відповіді.
Матвій

1

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

  • якщо у вас є більше одного db на redis, вам слід визначити базу даних, використовуючи -n [number]
  • якщо у вас є кілька клавіш, delале якщо є тисячі або мільйони ключів, краще використовувати, unlinkоскільки від’єднання не блокується, а дель блокується, для отримання додаткової інформації відвідайте цю сторінку від’єднайте від дель
  • також keysє як del і блокує

тому я використовував цей код для видалення ключів за шаблоном:

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

0

атомна маса бідолахи видалити?

можливо, ти зможеш налаштувати їх на ДОСЛІДЖЕННЯ тієї ж секунди - як кілька хвилин у майбутньому - і потім зачекати до цього часу і побачити, як вони всі "самознищуються" одночасно.

але я не дуже впевнений, якою це буде атомна.


0

Оголошення тепер, ви можете використовувати клієнт Redis і виконувати спочатку SCAN (підтримує узгодження шаблону), а потім DEL кожну клавішу окремо.

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


-1

Я підтримую всі відповіді, пов'язані з тим, щоб мати якийсь інструмент або виконати вираз Lua.

Ще один варіант з мого боку:

У наших виробничих та передвиробничих базах даних є тисячі ключів. Час від часу нам потрібно видалити деякі клавіші (за якоюсь маскою), змінити за якимись критеріями і т.д.

Для цього я пишу інструмент для клієнта java, який робить все це. У разі видалення ключів утиліта може бути дуже простою, там є лише один клас:

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

}


-3

Весна RedisTemplate сама забезпечує функціональність. В останній версії RedissonClient застаріла функція "deleteByPattern".

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

2
Я оновив зразок коду Redisson. Ваш код не є атомним підходом, як це робить Redisson. Між ними можуть з’являтися нові ключі keysта deleteвиклики методів.
Микита Кокшаров
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.