如何使用Redis自动删除与模式匹配的键


575

在我的Redis数据库中,我有很prefix:<numeric_id>多哈希值。

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


嗨,史蒂夫,您的网站有问题,我已经将其添加到其他博客mind-geek.net/nosql/redis/delete-keys-specific-expiry-time,希望这会有所帮助。
Gaurav Tewari 2013年

43
这种情况很常见,我希望Redis团队可以考虑为其添加本机命令。
Todd Menier 2013年

如今,您可以使用Lua做到这一点,请参见下文。
亚历山大·格拉迪什

3
@ToddMenier刚刚建议,把这个理由解释为永远不会发生:github.com/antirez/redis/issues/2042
Ray

1
很多人问有关如何处理大量键,带有特殊字符的键等相关问题。由于我们现在遇到这个问题,所以我创建了一个单独的问题,我不认为答案会发布在这个问题上。这里是另一个问题:stackoverflow.com/questions/32890648/...
jakejgordon

Answers:


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
@Ray坦率地说,如果您需要该功能,则只需按数字数据库或服务器对数据进行分区,然后使用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),以便您的应用程序将新数据写入键prefix:3:1和prefix:3:2。然后,您可以安全地从前缀:2:1和前缀:2:2中获取旧值,并清除旧键。


14
抱歉,但这不是原子删除。有人可能会在KEYS和DEL之间添加新的密钥。我不想删除那些。
亚历山大·格拉迪什

36
在KEYS命令之后创建的键不会被删除。
Casey

6
我只需要清除一些错误的键,所以Casey的第一个答案就很明确,除了我必须将键移到引号之外:redis-cli KEYS“ prefix:*” | xargs redis-cli DEL
jslatts 2011年

19
第一个答案也帮助了我。另一个变体,如果您的redis密钥包含引号或其他使xargs混乱的字符:redis-cli KEYS "prefix:*" | xargs --delim='\n' redis-cli DEL
过度考虑2011年

18
如果您有多个数据库(键空间),那么这就是窍门:假设您需要删除db3中的键:redis-cli -n 3 KEYS "prefix:*" | xargs redis-cli -n 3 DEL
Christoffer

73

这是在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

正如Kikito建议的那样。


10
如果您有大量的键,则上面的代码将无效(错误是“要拆包的元素太多”)。我建议在Lua部分使用循环:for _,k in ipairs(redis.call('keys', KEYS[1])) do redis.call('del', k) end
kikito 2013年

@kikito,是的,如果lua无法将堆栈增加到要删除的键数(很可能是由于内存不足),则需要使用for循环来完成。除非您必须这样做,否则我不建议您这样做。
伊莱(Eli)

1
Lua unpack转换了“独立变量列表”中的表(其他语言称为explode),但是最大数量不依赖于系统内存。它通过LUAI_MAXSTACK常量固定在lua中。在Lua 5.1和LuaJIT中,它是8000;在Lua 5.2中,它是100000。for IMO推荐使用for循环选项。
kikito

1
值得注意的是,lua脚本仅在Redis 2.6及更高版本中可用
wallacer

1
任何基于Lua的解决方案都将违反的语义,EVAL因为它没有预先指定将要操作的键。它应该在单个实例上工作,但是不要期望它与Redis Cluster一起工作。
凯文·克里斯托弗·亨利

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
Rob Johansen

请注意,如果您的密钥包含特殊字符,则此方法不起作用
mr1031011 '18

有趣且有价值的发现...我想知道是否有一种方法可以为xargs引用事物...
Itamar Haber

-L 100有什么作用??
阿帕纳(Aparna)

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

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

谢谢 :)


23

@mcdizle的解决方案不起作用,它仅适用于一个条目。

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

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

注意:您应该将“ prefix”替换为您的密钥前缀...


2
使用lua比使用xargs快很多,顺序为10 ^ 4。
deepak '17

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'

EX-“ 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. 闪存从连接的CURRENT数据库中删除数据。
  2. 冲洗从所有数据库中删除数据。

例如:-在您的shell中:

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的回答很好,但是解析回复对我来说不起作用,尤其是。如果在给定扫描中没有找到密钥。直接从控制台获得一个可能更简单的解决方案:

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没有这种功能。

我所做的:将会话数据存储在hash中。我刚刚创建的散列ID的哈希值session:sessionid,然后我推key-xkey-ykey-z在哈希(为了没事情我),如果我不需要散列了我只是做了DEL session:sessionid,并与哈希ID相关联的所有数据都将消失。DEL是原子的,访问数据/将数据写入哈希是O(1)。


好的解决方案,但我的价值观是散列本身。Redis将哈希存储在另一个哈希中。
亚历山大·格拉迪什

3
但是,哈希中的字段缺少过期功能,有时这确实很有用。
Evi Song

对我来说,这是迄今为止最干净/最简单的答案
Sebastien H.

一套更有意义吗?
Jack Tuck


5

仅供参考。

  • 仅使用bash和 redis-cli
  • 不使用keys(使用scan
  • 集群模式下运作良好
  • 不是原子的

也许您只需要修改大写字母。

扫描匹配

#!/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

清除-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

5

例如,如果您的密钥包含特殊字符,则其他答案可能不起作用Guide$CLASSMETADATA][1]。将每个键括在引号中将确保将其正确删除:

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

2
该脚本可以完美地运行,并通过超过25000个键进行了测试。
Jordi

1
您还可以使用这个有趣的表达式'awk'{print“'”'“'” $ 1“'”“”“}'`在awk中添加单引号
Roberto Congiu

3

使用SCAN而非KEYS的版本(建议用于生产服务器),并且 --pipe xargs的版本。

与xargs相比,我更喜欢使用管道,因为它效率更高,并且当您的键包含带引号或其他特殊字符的shell可以使用try和解释时,它更有效。此示例中的正则表达式替换将键括在双引号中,并在其中转义任何双引号。

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

即使在大约7m的按键上,该解决方案也对我有效!
丹尼

2

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

如果您必须匹配数千万或数亿个键,则此处给出的答案将导致Redis在相当长的时间内(几分钟?)无响应,并可能由于内存消耗而崩溃(请确保后台保存会在操作过程中加入)。

不可否认,以下方法很难看,但我没有找到更好的方法。这里没有原子性,在这种情况下,主要目标是保持Redis正常运行并100%响应。如果您将所有密钥都放在一个数据库中,并且不需要匹配任何模式,但由于它具有阻塞性,则不能使用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


2

请使用此命令并尝试:

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

不是原子的,并且重复其他答案。
马修·

1

我尝试了上面提到的大多数方法,但是它们对我不起作用,经过一些搜索,我发现了以下几点:

  • 如果在redis上有多个数据库,则应使用 -n [number]
  • 如果您使用的是几个键,del但是如果有成千上万个键,则最好使用,unlink因为在del 阻止unlink是非阻塞的,有关更多信息,请访问此页面unlink vs del
  • keys像del并且正在阻止

所以我用下面的代码按模式删除密钥:

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

0

穷人的原子质量删除?

也许您可以将它们全部设置为在同一秒内过期(例如在未来几分钟内),然后等到那个时候看到它们同时都在“自我毁灭”。

但我不确定这将是多么原子。


0

现在,您可以使用Redis客户端并先执行SCAN(支持模式匹配),然后分别对每个密钥进行DEL。

但是,官方redis github上存在一个在此处创建patter-matching-del的问题,如果发现有用的话,请向它展示一些爱!


-1

我支持与拥有某些工具或执行Lua表达式有关的所有答案。

我这边还有一个选择:

在我们的生产和生产前数据库中,有成千上万的密钥。我们有时需要删除一些键(通过某些掩码),通过某些条件进行修改等。当然,无法从CLI手动进行操作,尤其是具有分片(每个物理分区中有512个逻辑数据库)的情况。

为此,我编写了完成所有这些工作的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();
    }

}

-1

下面的命令为我工作。

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

-3

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

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

2
我更新了Redisson示例代码。您的代码不像Redisson那样采用原子方法。在keysdelete方法调用之间可能会出现新的键。
Nikita Koksharov 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.