之前我们的服务,在上线的时候发现有一些大Key的使用不是很规范,特别是没有设置过期时间,因此导致redis中内存的数据越来越多,目前Redis节点的内存已经快撑不住了。所以根据缓存键的规则去批量删除这些数据,比较常见的就是按前缀去删除。
现在由于不得以为的原因要删除这几百个Key-Value的数据,这个时候我们肯定就要把缓存键全部删除掉。一般情况下在Redis中是可以很容易去实现的。但是如果在不阻塞业务的前提下,并且以高效的方式进行清理内存数据。就需要好好想想办法了。
(相关资料图)
我们可以通过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:是一条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
如果以上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是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
有兴趣的小伙伴,可以参考:http://redis.cn/commands/eval.html
EVAL script numkeys key [key …] arg [arg …]script:待执行的脚本文件numkeys:key的个数
[key …]
:对应的key,可以是一个,可以是多个[arg …]
:与key对应的值,可以是一个,可以是多个Lua的下表索引是从1开始的,key的获取方式,KEYS[下标索引],如KEYS[1],取第一个值,值的获取,ARGV[1]
eval “return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}” 2 key1 key2 first second
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 endelse return 0end
推荐使用scan获取数据删除,我们知道redis是一个单线程的,当我们库里面存在大量数据的时候,使用keys * 的方式匹配数据的时候,可能需要好几秒才能处理完,那么在这个几秒的时间里是处于线程阻塞的,其他所有的redis操作都是处于等待状态,这样对系统的可用性是有影响的,因此,这里使用scan的方式匹配数据。
SCAN cursor [MATCH pattern] [COUNT count]
SCAN 命令是一个基于游标的迭代器(cursor based iterator): SCAN 命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。
local limitSize = tonumber(ARGV[1]) -- 最多删除多少个keylocal batchSize = limitSize -- scan一次最多扫描多少个keyif (batchSize > 10000) then -- 一次扫描不能超过1w条 batchSize = 10000endlocal 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 keynumendlocal total = scan(KEYS[1])return total
当 SCAN 命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。
通俗点理解就是,基于游标的迭代器redis会慢慢一次次的将数据返回回来,从而防止线程阻塞。
此外还有一个小贴士就是可以使用UNLINK删除,区别于del的是这个是异步执行的,这条指令要版本大于4.0.0 小于4.0.0就使用del
redis.call("UNLINK",key)