8

【Redis技术专区】「实战案例」谈谈使用Redis缓存时高效的批量删除的几种方案

 2 years ago
source link: https://blog.51cto.com/alex4dream/6021101
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
【Redis技术专区】「实战案例」谈谈使用Redis缓存时高效的批量删除的几种方案_Lua

之前我们的服务,在上线的时候发现有一些大Key的使用不是很规范,特别是没有设置过期时间,因此导致redis中内存的数据越来越多,目前Redis节点的内存已经快撑不住了。所以根据缓存键的规则去批量删除这些数据,比较常见的就是按前缀去删除。

现在由于不得以为的原因要删除这几百个Key-Value的数据,这个时候我们肯定就要把缓存键全部删除掉。一般情况下在Redis中是可以很容易去实现的。但是如果在不阻塞业务的前提下,并且以高效的方式进行清理内存数据。就需要好好想想办法了。

批量删除redis数据方法

利用的是Linux的xargs命令

我们可以通过redis-cli的模式,进行访问之后登录到了Redis-Server服务,由于是必须要使用Linux的xargs命令,所以必须要连带指令在Linux环境,而不能提前通过redis-cli进行登录到redis-server服务。否则会报错说​​xargs​​无效。

redis-cli -h [ip] -p [port ] -a [password] keys "prefix*" | xargs redis-cli -h 127.0.0.1 -p 6379 -a '123' del

上面的指令主要由三部分连接组成:

  • redis-cli -h [ip] -p [port ] -a [password]:主要需要用于登录到redis-cli的只处理操作。
  • keys "prefix*":随后主要是通过redis-cli的命令进行 keys指令进行匹配某前缀相关的数据集合。
  • | xargs redis-cli -h [ip] -p [port ] -a [password] del:主要是通过管道符进行连接,之后再进行连接redis-server服务,之后进行将之前的参数传入到xargs之后,作为del的参数进行执行删除操作。
xargs指令

xargs:是一条Unix和类Unix操作系统的常用命令;它的作用是将参数列表转换成小块分段传递给其他命令,以避免参数列表过长的问题。可单独使用,也可使用管道符、重定位符等与其他命令配合使用。

xargs [ -p ] [ -t] [ -e[ EOFString ] ] [ -EEOFString ] [ -i[ ReplaceString ] ] [ -IReplaceString ] [ -l [ Number ] ] [ -L Number ] [ -n Number [ -x ] ] [ -s Size ] [ Command [ Argument ... ] ]

xargs:一般是和管道一起使用。

somecommand |xargs -item command
  • -a file 从文件中读入作为 stdin
  • -e flag ,注意有的时候可能会是-E,flag必须是一个以空格分隔的标志,当xargs分析到含有flag这个标志的时候就停止。
  • -p 当每次执行一个argument的时候询问一次用户。
  • -n num 后面加次数,表示命令在执行的时候一次用的argument的个数,默认是用所有的。
  • -t 表示先打印命令,然后再执行。
  • -i 或者是-I,这得看linux支持了,将xargs的每项名称,一般是一行一行赋值给 {},可以用 {} 代替。
  • -r no-run-if-empty 当xargs的输入为空的时候则停止xargs,不用再去执行了。
  • -s num 命令行的最大字符数,指的是 xargs 后面那个命令的最大命令行字符数。
  • -L num 从标准输入一次读取 num 行送给 command 命令。
  • -l 同 -L。
  • -d delim 分隔符,默认的xargs分隔符是回车,argument的分隔符是空格,这里修改的是xargs的分隔符。
  • -x exit的意思,主要是配合-s使用。。
  • -P 修改最大的进程数,默认是1,为0时候为as many as it can ,这个例子我没有想到,应该平时都用不到的吧。

使用Lua脚本删除百万/千万级的key

如果以上xargs方法删除不了的,或者执行xargs命令报错的。那么可以使用lua脚本,redis有内置的lua解释器。在lua脚本中使用scan扫描key,并依次删除,当删除数量达到1万时,脚本直接返回,完成本次调用,如果删除的key数量大于0,就循环调用脚本进行删除。

Warning: a NUL character occurred in the input. It cannot be passed through in the argument list. Did you mean to use the --null option?
Lua脚本是什么?

Lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

Lua脚本的指令格式

有兴趣的小伙伴,可以参考:​ ​http://redis.cn/commands/eval.html​

EVAL script numkeys key [key …] arg [arg …]
  • script:待执行的脚本文件
  • numkeys:key的个数
Lua脚本执行参数
  • ​[key …]​​:对应的key,可以是一个,可以是多个
  • ​[arg …]​​:与key对应的值,可以是一个,可以是多个
Lua获取传参数据

Lua的下表索引是从1开始的,key的获取方式,KEYS[下标索引],如KEYS[1],取第一个值,值的获取,ARGV[1]

eval “return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}” 2 key1 key2 first second
Lua脚本的案例(keys)
  1. 获取传入的需要批量删除的key的前缀
  2. 记住 lua的下标索引是从1开始 不是0 不是0 不是0
local key = KEYS[1]
if( key ~= nil) then
--这里通过keys查询出所有符合条件的数据
local dataList = redis.call('keys',KEYS[1])
--判断是否找到数据
if(dataList ~= nil) then
--循环删除
for i=1,#dataList,1 do
redis.call('del',dataList[i])
end
--返回删除的行数
return #dataList
else
return 0
end
else
return 0
end

推荐使用scan获取数据删除,我们知道redis是一个单线程的,当我们库里面存在大量数据的时候,使用keys * 的方式匹配数据的时候,可能需要好几秒才能处理完,那么在这个几秒的时间里是处于线程阻塞的,其他所有的redis操作都是处于等待状态,这样对系统的可用性是有影响的,因此,这里使用scan的方式匹配数据。

scan介绍
SCAN cursor [MATCH pattern] [COUNT count]

SCAN 命令是一个基于游标的迭代器(cursor based iterator): SCAN 命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命
令的游标参数, 以此来延续之前的迭代过程。

Lua脚本的案例(scan)
local limitSize = tonumber(ARGV[1]) -- 最多删除多少个key
local batchSize = limitSize -- scan一次最多扫描多少个key
if (batchSize > 10000) then -- 一次扫描不能超过1w条
batchSize = 10000
end
local function scan(key)
local cursor = 0
local keynum = 0
repeat
local res = redis.call("scan", cursor, "match", key, 'COUNT', batchSize)
if (res ~= nil and #res >= 0) then
redis.replicate_commands()
cursor = tonumber(res[1])
local ks = res[2]
local size = #ks
for i=1,size,1 do
redis.call("del", tostring(ks[i]))
keynum = keynum + 1
if (keynum >= limitSize) then -- 已经删除了指定数量的key, 返回
return keynum
end
end
end
until (cursor <= 0)
return keynum
end
local total = scan(KEYS[1])
return total

当 SCAN 命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。

通俗点理解就是,基于游标的迭代器redis会慢慢一次次的将数据返回回来,从而防止线程阻塞。

此外还有一个小贴士就是可以使用UNLINK删除,区别于del的是这个是异步执行的,这条指令要版本大于4.0.0 小于4.0.0就使用del

redis.call("UNLINK",key)

Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK