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

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

有时我想原子地清除它们。如何在不使用分布式锁定机制的情况下做到这一点?

550573 次浏览

在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中获取旧值并清除旧键。

我认为可能对您有帮助的是多/执行/丢弃。虽然不是100%等值交易,但您应该能够将删除与其他更新隔离开来。

我只是遇到了同样的问题。我以以下格式存储用户的会话数据:

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

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

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

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

这是一个在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:

这是麦克迪泽在回答这个问题时的想法的工作版本。这个想法100%归功于他。

编辑:根据Kikito下面的评论,如果您的Redis服务器中要删除的密钥比空闲内存多,您将遇到“无法解包的元素太多”错误。在这种情况下,请执行:

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

正如Kikito建议的那样。

穷人的原子质量删除?

也许你可以把它们都设置为同一秒——就像未来的几分钟——然后等到那个时候,看到它们同时“自毁”。

但我不确定这会有多原子。

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

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

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

从v2.8开始,您真的希望使用扫描命令而不是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]钥匙是一个可能导致DoS的危险命令。以下是其留档页面的引用:

警告:将KEYS视为一个只能在生产环境中极其小心地使用的命令。当它对大型数据库执行时可能会破坏性能。此命令用于调试和特殊操作,例如更改密钥空间布局。不要在常规应用程序代码中使用KEYS。如果您正在寻找在密钥空间的子集中查找密钥的方法,请考虑使用集合。

更新:一个衬里相同的基本效果-

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

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

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

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

备注:您应该用密钥前缀替换“前缀”…

如果键名中有空格,可以在bash中使用:

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

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

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

这也使用SCAN,它在生产中比KEYS更可取,但不是原子的。

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

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

这不是对问题的直接回答,但由于我在寻找自己的答案时来到这里,我将在这里分享这个。

如果您有数千万或数亿个密钥需要匹配,这里给出的答案将导致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中使用随机键的示例实现,作为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表达式相关的答案。

我这边还有一个选择:

在我们的生产和预生产数据库中有数千个键。有时我们需要删除一些键(通过一些掩码),修改一些标准等。当然,没有办法从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();
}


}

您还可以使用此命令删除密钥:-

假设你的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. FLUSHDB-从连接的CURRENT数据库中删除数据。
  2. FLUSHALL-从所有数据库中删除数据。

例如:-在您的shell中:

redis-cli flushall
redis-cli flushdb

仅供参考。

  • 仅使用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 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);
}
}

谢谢你:)

使用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

请使用此命令并尝试:

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

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

  • 如果您在redis上有多个db,则应使用-n [number]确定数据库
  • 如果你有几个键使用del,但如果有数千或数百万个键,最好使用unlink,因为解除关联是非阻塞的同时del被阻塞,有关更多信息,请访问此页面解除关联vs del
  • 还有keys就像del并且正在阻塞

所以我使用这段代码按模式删除键:

 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

现在,您可以使用redis客户端并执行第一个SCAN(支持模式匹配),然后逐个DEL每个键。

但是,官方redis github上有一个问题,可以创建一个patter-matching-del这里,如果你觉得有用,就去给它一些爱!

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

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

如果您使用Windows环境,请按照以下步骤操作,它肯定会起作用:

  1. 从这里下载GOW-https://github.com/bmatzelle/gow/wiki(因为xargs命令在Windows中不起作用)

  2. 下载适用于Windows的redis-cli(详细说明在这里-https://medium.com/@binary101110106b

  3. 运行cmd并打开redis-cli存储的目录(例如:D:\Redis\Redis-x64-3.2.100)

  4. 如果您想删除所有以“Global: ProviderInfo”开头的密钥,请执行此查询(需要更改粗体参数(主机、端口、密码、密钥)并编写您的密钥,因为这只是示例):

    redis-cli-hredis.test.com-p6379-a密码--原始键"全球:供应商信息*"|xargs redis-cli-hredis.test.com-p6379-a密码 del

// TODO

你认为它的命令没有意义,但是有时Redis命令像DEL不正确工作,并来拯救这个

redis-cli KEYS "*" | xargs -i redis-cli EXPIRE {} 1生活黑客

如果您的键名中有空格,这将适用于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

我用EVAL命令的最简单变体成功了这一点:

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

我用我的值替换了my_pattern_here

添加到这个答案:

要查找前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_*

如果您使用的是Redis版本低于4,您可以尝试

redis-cli -h 127.0.0.1 -p 26379 -a `yourPassword` --scan --pattern data:* | xargs redis-cli del

如果您使用的是上述4个版本,则

redis-cli -h 127.0.0.1 -p 26379 -a `yourPassword` --scan --pattern data:*| xargs redis-cli unlink

要检查您的版本,请使用以下命令输入您的Redis终端

redis-cli -h 127.0.0.1 -p 26379 -a `yourPassword

然后键入

> INFO


# Server
redis_version:5.0.5
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:da75abdfe06a50f8
redis_mode:standalone
os:Linux 5.3.0-51-generic x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:7.5.0
process_id:14126
run_id:adfaeec5683d7381a2a175a2111f6159b6342830
tcp_port:6379
uptime_in_seconds:16860
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:15766886
executable:/tmp/redis-5.0.5/src/redis-server
config_file:


# Clients
connected_clients:22
....More Verbose

这是我想到的最简单的方法,不用任何xargs魔法

纯粹的狂欢!

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

如果我们想确保原子操作,我们可以尝试编写Lua脚本。

如果您的Redis版本支持SCANUNLINK0(高于4.0.0),我更喜欢在正式生产环境中使用SCANUNLINK0而不是KeyDEL,因为KeyDEL命令可能会阻塞

它们可以在生产环境中使用,而不会出现KEYS或SMMBERS等命令的缺点,当针对大量键或元素调用时,这些命令可能会长时间(甚至几秒钟)阻塞服务器。

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:*