redis 通过lua脚本批量删除指定key

背景

原因是生产环境报错

MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error..

翻译一下就是在进行rdb备份的时候出现异常,导致上层应用不能正常访问redis,可以通过把stop-writes-on-bgsave-error设置为no解决,我的理解是no就是忽略了rdb备份的这个异常,并没有根本性的解决问题.

查看redis内存情况之后发现redis已经存了4.7g的数据,远超过预期,依次排查了redis的内存碎片率过高等可能的问题,没有发现异常.

最后定位到是某一个组件为了做去重处理,把之前接收的数据id都往redis放了一份,大致估了下有1亿多条,于是就考虑把较早的一批数据删掉,在网上找了点资料,综合整理写了个lua脚本把问题解决了.

解决方法

根据生产环境抽象了一下需要删除的数据结构,通过以下脚本构造模拟数据

add.lua

redis.call('select','8')
local start=10000
while (start<20000)
do
	redis.call("hset","testAuto",start,"test value")
	start=start+1
end

将以上内容保存为文本,并以.lua为扩展名,执行方法如下

redis-cli -a "password" --eval add.lua

脚本跑完之后,redis中数据如下:

这是以个key=自增数字,value="test value"的集合,根据现场情况,需要删除key小于某一个值的所有值

以下lua脚本是删除所有key小于15000的数据,共计5000条

del.lua

--切换倒目标数据库
redis.call('select',8)
redis.replicate_commands()
--需要删除的hash集合名称
local mapName="testAuto"
--小于这个id的值都会被删除
local lastThumbnailId=15000
local myCursor=0
while(true)
do
    --日志输出到redis的日志文件中
    redis.log(redis.LOG_WARNING,"myCursor   ".. myCursor)
    --hscan有一个小问题,如果哈希表中的数据量不够多(据网友说小于513,具体没有验证)的时候,count会不生效,直接返回所有的key和value
    local iterator=redis.call('hscan',mapName,myCursor,'count',1000)
    for index,keyOrValue in pairs(iterator) do
        redis.log(redis.LOG_WARNING,"index   "..index)
        --redis.log(redis.LOG_WARNING,"v"..v)
        if (index==1)
        then
			myCursor=tonumber(keyOrValue)
        end
        if (index==2)
            then
            for subIndex,subKeyOrValue in pairs(keyOrValue) do
                --redis.log(redis.LOG_WARNING,"subIndex   "..subIndex)
                if (type(tonumber(subKeyOrValue)) == "number" and tonumber(subKeyOrValue)<lastThumbnailId )
                then
                --redis.log(redis.LOG_WARNING,"subKeyOrValue   "..subKeyOrValue)
                    redis.call('hdel',mapName,subKeyOrValue)
                end
            end
        end
    end
    --如果所有的数据都跑完了,那么写一次cursor会重新变成0
    if(myCursor==0)
    then
        break
    end
end

简单说一下这个pairs函数的用法,对照上面代码的变量我标注了一下

跑完del.lua脚本之后可以看到testAuto中数据只有5000条了,删除成功

对于 redis.replicate_commands()这一行代码也提一下,如果没有会产生如下报错

(error) ERR Error running script (call to f_13a8e8ef956cadf3b0b1c5a2c25337099126724b): @user_script:27: @user_script: 27: Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode.

原因是hscan在redis中是属于一个随机性命令,这个命令的返回值是不固定的(相同的脚本执行多次可能会产生不同的结果),这种情况会对数据一致性造成破坏,

具体原因可以参考这篇文章redis4.0之Lua脚本新姿势,非常详细.

posted on 2020-09-29 15:01  precedeforetime  阅读(3075)  评论(0编辑  收藏  举报

导航