如何使用Redis原子地删除符合模式的键

768

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

有时候我想要原子性地清除它们所有。如何在不使用任何分布式锁机制的情况下实现这一点?


嗨,史蒂夫,我的网站出了一些问题,我已经将它添加到我的其他博客http://www.mind-geek.net/nosql/redis/delete-keys-specific-expiry-time,希望这可以帮助解决问题。 - Gaurav Tewari
106
这种情况非常普遍,我希望Redis团队考虑添加一条原生命令来解决它。 - Todd Menier
现在你可以用Lua轻松实现这个功能,见下文。 - Alexander Gladysh
4
@ToddMenier 刚提出了一个建议,收到了这个回复,解释为什么它永远不会发生:https://github.com/antirez/redis/issues/2042 - Ray
1
很多人在询问如何处理大量的键、带有特殊字符的键等相关问题。由于我们现在遇到了这个问题,而且我认为答案没有发布在这个问题上,所以我创建了一个单独的问题。这是另一个问题的链接:https://dev59.com/YFwY5IYBdhLWcg3wM1bK - jakejgordon
显示剩余2条评论
34个回答

881

在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写入新数据。然后,您可以安全地从prefix:2:1和prefix:2:2中获取旧值并清除旧键。


24
抱歉,但这不是原子删除。在 KEYS 和 DEL 之间可能会添加新的键。我不想删除那些键。 - Alexander Gladysh
48
执行KEYS命令后创建的键将不会被删除。 - Ilia Kondrashov
6
我只需要清除一些坏键,所以Casey的第一个答案是正确的,只是我需要将键移动到引号外面:redis-cli KEYS "prefix:*" | xargs redis-cli DEL。 - jslatts
29
第一个答案也对我有所帮助。如果你的 Redis 键包含引号或其他会干扰 xargs 的字符,可以尝试另一种变体:redis-cli KEYS "prefix:*" | xargs --delim='\n' redis-cli DEL - overthink
23
如果您有多个数据库(键空间),则可以使用以下技巧:假设您需要删除db3中的键:“redis-cli -n 3 KEYS“ prefix:*”| xargs redis-cli -n 3 DEL”。 - Christoffer
显示剩余17条评论

497

从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文档


28
重要提示:如果您有超过几千个与前缀匹配的键,则此方法将失败。 - Nathan Osman
113
这段代码可处理大量密钥: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
256
哎呀... Redis 经常被用作简单的键/值缓存。这似乎意味着 del prefix:* 应该成为一项基本操作 :/ - Ray
8
@Ray 如果你需要那个功能,你应该简单地按数字数据库或服务器对数据进行分区,并使用flush/flushdb。 - Marc Gravell
15
如果没有键与模式匹配,那么它将失败。为了解决这个问题,我添加了一个默认键:EVAL "return redis.call('del', 'defaultKey', unpack(redis.call('keys', ARGV[1])))" 0 prefix:* - manuelmhtr
显示剩余10条评论

89
以下是一份完全可用且原子化的通配符删除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在回答这个问题中提出的想法的工作版本。完全归功于他。

编辑:根据Kikito在下面的评论中所说,如果你要删除的键比Redis服务器上的可用内存还多,你会遇到“太多元素无法解包”错误。在这种情况下,请执行:

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

如Kikito所建议。

13
如果您有大量的键(错误为“元素过多无法解包”),上面的代码将崩溃。我建议在Lua部分使用循环:for _,k in ipairs(redis.call('keys', KEYS[1])) do redis.call('del', k) end - kikito
@kikito,是的,如果由于内存不足而无法将Lua栈扩展到要删除的键数(最有可能是这种情况),则您需要使用for循环来完成此操作。除非必须,否则我不建议这样做。 - Eli
2
Lua中的unpack函数将一个表转换为“独立变量列表”(其他语言称之为explode),但其最大数量不受系统内存影响。在Lua中,它通过LUAI_MAXSTACK常量进行固定。在Lua 5.1和LuaJIT中,它是8000,在Lua 5.2中是100000。在我看来,建议使用for循环选项。 - kikito
1
值得注意的是,Lua脚本仅适用于Redis 2.6及以上版本。 - wallacer
5
任何基于 Lua 的解决方案都会违反 EVAL 命令的语义,因为它不预先指定要操作的键。它应该可以在单个实例上运行,但不能保证在 Redis 集群中工作。 - Kevin Christopher Henry

83

免责声明:以下解决方案不提供原子性。

从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。如果您正在寻找在键空间子集中查找键的方法,请考虑使用 sets。

更新: 相同基本效果的单行代码 -

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

12
尽管如此,避免使用KEYS绝对被视为最佳实践,因此只要非原子删除可行,这就是一个很好的解决方案。 - fatal_error
这对我起作用了,但是我的键恰好在数据库1中。因此,我必须在每个redis-cli调用中添加-n 1redis-cli -n 1 --scan --pattern“*:foo:bar:*”| xargs -L 100 redis-cli -n 1 DEL - Rob Johansen
请注意,如果您的键包含特殊字符,则此方法无法正常工作。 - mr1031011
有趣且有价值的发现...我想知道是否有一种方法可以为xargs引用事物... - Itamar Haber
这种方法在许多情况下都会失败,包括模式未找到任何内容的情况。创建了一个更安全的版本。https://gist.github.com/dk8996/1f8c92c4c5ea1b4e8997b80e826b90f4 - Dimitry
显示剩余2条评论

66

对于那些阅读其他答案有困难的人:

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

key:*:pattern 替换为你自己的模式,然后输入到 redis-cli 中,你就可以开始了。

感谢来自 lisco 的贡献:http://redis.io/commands/del


47

我正在使用Redis 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);
            }
        }

谢谢你 :)


这对我没有任何作用。 - chovy
redis-cli keys '*'|xargs redis-cli del 运行正常。 - Ismail

39

您也可以使用此命令删除键:

假设您的 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 - 从所有数据库中删除数据。

例如:在您的shell中:

redis-cli flushall
redis-cli flushdb

3
谢谢,但将输出传输到 redis-cli del 不是原子性的。 - Alexander Gladysh
2
如果键值包含空格或双引号,则无法正常工作。 - chovy
我必须这样做才能让它工作:redis-cli --scan --pattern 'xyz_category_fpc*' | xargs -I{} redis-cli del '{}' - Raza

29
@mcdizle的解决方案并不起作用,它只适用于一个条目。这个可以适用于所有具有相同前缀的键。
EVAL "for i, name in ipairs(redis.call('KEYS', ARGV[1])) do redis.call('DEL', name); end" 0 prefix*

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


4
使用Lua比使用xargs快得多,速度可以达到10^4的数量级。 - deepak
坚实的解决方案! - Hendry

19
我用最简单的EVAL命令变体成功地完成了这个操作。
EVAL "return redis.call('del', unpack(redis.call('keys', 'my_pattern_here*')))" 0

在这里,我用我的值替换了my_pattern_here


3
这个方法有效,但我必须使用单引号。例如:EVAL "return redis.call('del', unpack(redis.call('keys', 'my_pattern_here*')))" 0 - Andy
对于那些试图清理但遇到以下错误的人: (error) ERR Error running script (call to ...): @user_script:1: user_script:1: too many results to unpack, try a solution from comments of the similar answer above. - Riki_tiki_tavi
终于有东西可以用了,谢谢! - Brunis

17

如果你的键名中含有空格,你可以在bash中使用以下方法:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接