首页 文章

如何使用Redis以原子方式删除与模式匹配的键

提问于
浏览
487

在我的Redis DB中,我有许多 prefix:<numeric_id> 哈希 .

有时我想以原子方式清除它们 . 如何在不使用分布式锁定机制的情况下执行此操作?

21 回答

  • 635

    在bash中执行:

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

    UPDATE

    好,我明白了 . 这种方式:存储当前额外的增量前缀并将其添加到您的所有键 . 例如:

    你有这样的 Value 观:

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

    当您需要清除数据时,首先更改prefix_actuall(例如set prefix_prefix_actuall = 3),这样您的应用程序就会将新数据写入密钥前缀:3:1和前缀:3:2 . 然后,您可以安全地从前缀:2:1和前缀:2:2中取旧值并清除旧密钥 .

  • -1

    它很容易通过FastoRedis中的"Remove branch"功能实现,只需选择要删除的分支 .

    enter image description here

  • 8

    穷人的原子质量删除?

    也许你可以将它们全部设置为EXPIREAT相同的秒 - 比如将来的几分钟 - 然后等到那个时候,看到它们同时“自我毁灭” .

    但我不确定那将是多么原子 .

  • 366

    我刚遇到同样的问题 . 我以下列格式存储了用户的会话数据:

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

    因此,每个条目都是一个单独的键值对 . 当会话被销毁时,我想通过删除模式为 session:sessionid:* 的键来删除所有会话数据 - 但是redis没有这样的功能 .

    我做了什么:将会话数据存储在hash中 . 我只是创建一个哈希id为 session:sessionid 的哈希,然后我在那个哈希中推送 key-xkey-ykey-z (顺序对我来说无关紧要)如果我不再需要那个哈希,我只需要做 DEL session:sessionid 以及与之关联的所有数据哈希id消失了 . DEL 是原子的,访问数据/写入哈希的数据是O(1) .

  • 0

    从redis 2.6.0开始,您可以运行原子执行的lua脚本 . 我从来没有写过,但我觉得它看起来像这样

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

    EVAL documentation .

  • 1

    请使用此命令并尝试:

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

    我认为可能对你有帮助的是MULTI/EXEC/DISCARD . 虽然不是100% equivalent of transactions,但您应该能够将删除与其他更新隔离开来 .

  • -2

    免责声明:以下解决方案 doesn't 提供原子性 .

    从v2.8开始, really 想要使用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 . 如果您正在寻找一种在键空间子集中查找键的方法,请考虑使用集合 .

    UPDATE: 一个衬垫具有相同的基本效果 -

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

    对于那些在解析其他答案时遇到问题的人:

    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

  • 55

    这是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%的想法归功于他 .

    EDIT: Per Kikito 's comment below, if you have more keys to delete than free memory in your Redis server, you' ll遇到"too many elements to unpack" error . 在这种情况下,做:

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

    正如Kikito建议的那样 .

  • 15

    我在redis 3.2.8中使用下面的命令

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

    您可以从此处获得与键模式搜索相关的更多帮助: - https://redis.io/commands/keys . 根据您的要求使用方便的glob样式模式,如 YOUR_KEY_PREFIXYOUR_KEY_PREFIX?? 或任何其他 .

    如果你们中的任何人已经集成 Redis PHP library 而不是以下功能将帮助你 .

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

    谢谢 :)

  • 31

    仅供参考 .

    • 仅使用bash和 redis-cli

    • 不使用 keys (这使用 scan

    • cluster mode 运作良好

    • not atomic

    也许你只需要修改大写字符 .

    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
    
  • 68

    redis-cli keys "*prefix*" 为我工作

  • -1

    You can also use this command to delete the keys:-

    假设您的redis中有许多类型的键,例如 -

    • 'xyz_category_fpc_12'

    • 'xyz_category_fpc_245'

    • 'xyz_category_fpc_321'

    • 'xyz_product_fpc_876'

    • 'xyz_product_fpc_302'

    • 'xyz_product_fpc_01232'

    Ex-' xyz_category_fpc '这里 xyzsitename ,这些密钥与电子商务网站的产品和类别相关,并由FPC生成 .

    如果您使用以下命令 -

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

    要么

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

    它删除所有键,如' xyz_category_fpc ' (delete 1, 2 and 3 keys). For delete other 4, 5 and 6 number keys use ' xyz_product_fpc '在上面的命令中 .

    如果你想在 RedisDelete Everything ,那么按照这些命令 -

    With redis-cli:

    • FLUSHDB - 从连接的CURRENT数据库中删除数据 .

    • FLUSHALL - 从所有数据库中删除数据 .

    例如: - 在你的shell中:

    redis-cli flushall
    redis-cli flushdb
    
  • 14

    @ itamar的回答很棒,但是回复的解析对我来说不起作用,尤其是 . 在给定扫描中没有找到键的情况下 . 一个可能更简单的解决方案,直接来自控制台:

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

    这也使用SCAN,它在 生产环境 中优于KEYS,但不是原子的 .

  • 2

    这不是问题的直接答案,但由于我在搜索自己的答案时到了这里,我将在这里分享 .

    如果你必须匹配数十或数亿个密钥,这里给出的答案将导致Redis在很长时间内没有响应(几分钟?),并且可能因内存消耗而崩溃(确保后台保存将会在你的操作过程中开始) .

    以下方法无疑是丑陋的,但我没有需要匹配任何模式,但由于它的阻塞性质,不能使用http://redis.io/commands/FLUSHDB .

    想法很简单:编写一个在循环中运行的脚本并使用O(1)操作(如http://redis.io/commands/SCANhttp://redis.io/commands/RANDOMKEY)来获取密钥,检查它们是否与模式匹配(如果需要)和http://redis.io/commands/DEL它们一个接一个 .

    如果有更好的方法,请告诉我,我会更新答案 .

    在Ruby中使用randomkey的示例实现,作为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
    
  • 31

    使用SCAN而不是KEYS(对于 生产环境 服务器推荐)和 --pipe 而不是xargs的版本 .

    我更喜欢管道覆盖xargs,因为它更高效,并且当你的密钥包含引号或其他特殊字符时,你的shell可以尝试和解释 . 此示例中的正则表达式替换将键包装在双引号中,并转义内部的任何双引号 .

    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
    
  • 10

    我支持所有与使用某个工具或执行Lua表达式相关的答案 .

    我方还有一个选择:

    在我们的 生产环境 和预 生产环境 数据库中,有数千个密钥 . 我们需要不时删除一些键(通过某些掩码),按某些标准修改等等 . 当然,没有办法从CLI手动完成,特别是有分片(每个物理中有512个逻辑dbs) .

    为此我编写了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();
        }
    
    }
    
  • 5

    @ mcdizle的解决方案不起作用它只适用于一个条目 .

    这个适用于具有相同前缀的所有密钥

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

    Note: 您应该用您的密钥前缀替换'prefix' ...

  • 5

    如果密钥名称中有空格,则可以在bash中使用:

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

    Spring RedisTemplate本身提供了这些功能 . 最新版本中的RedissonClient已弃用“deleteByPattern”功能 .

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

相关问题