Redis:使用scan配合lua脚本进行删除key

Redis SCAN 命令

Redis key(键)](https://www.runoob.com/redis/redis-keys.html "Redis key(键)")

Redis Scan 命令用于迭代数据库中的数据库键。

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

SCAN 返回一个包含两个元素的数组, 第一个元素是用于进行下一次迭代的新游标, 而第二个元素则是一个数组, 这个数组中包含了所有被迭代的元素。如果新游标返回 0 表示迭代已结束。

相关命令:

  • SSCAN 命令用于迭代集合键中的元素。
  • HSCAN 命令用于迭代哈希键中的键值对。
  • ZSCAN 命令用于迭代有序集合中的元素(包括元素成员和元素分值)。

语法

redis Scan 命令基本语法如下:

  • cursor - 游标。
  • pattern - 匹配的模式。
  • count - 指定从数据集里返回多少元素,默认值为 10 。

可用版本

= 2.8.0

返回值

数组列表。

scan和keys的区别

redis的keys命令,通来在用来删除相关的key时使用,但这个命令有一个弊端,在redis拥有数百万及以上的keys的时候,会执行的比较慢,更为致命的是,这个命令会阻塞redis多路复用的io主线程,如果这个线程阻塞,在此执行之间其他的发送向redis服务端的命令,都会阻塞,从而引发一系列级联反应,导致瞬间响应卡顿,从而引发超时等问题,所以应该在生产环境禁止用使用keys和类似的命令smembers,这种时间复杂度为O(N),且会阻塞主线程的命令,是非常危险的。

keys命令的原理就是扫描整个redis里面所有的db的key数据,然后根据我们的通配的字符串进行模糊查找出来。官网详细的介绍如下。

https://redis.io/commands/KEYS
取而代之的,如果需要查找然后删除key的需求,那么在生产环境我们应该使用scan命令,代替keys命令,同样是O(N)复杂度的scan命令,支持通配查找,scan命令或者其他的scan如SSCAN ,HSCAN,ZSCAN命令,可以不用阻塞主线程,并支持游标按批次迭代返回数据,所以是比较理想的选择。keys相比scan命令优点是,keys是一次返回,而scan是需要迭代多次返回。

https://redis.io/commands/scan
但scan命令的也有缺点,返回的数据有可能重复,需要我们在业务层按需要去重,scan命令的游标从0开始,也从0结束,每次返回的数据,都会返回下一次游标应该传的值,我们根据这个值,再去进行下一次的访问,如果返回的数据为空,并不代表没有数据了,只有游标返回的值是0的情况下代表结束。

关于key是删除

1、清楚某个服务或者某种资源的key,往往是用模糊匹配去清楚,使用场景:某个微服务中某写缓存发生了database级别的变化,在不停redis的情况下,如何高效的清除这些缓存。

2、redis大key,这里指的是大的集合数据类型,如(set/hash/list/sorted set),一个key包含很多元素。由于redis是单线程,在删除大key(千万级别的set集合)的时候,或者清理过期大key数据时,主线程忙于删除这个大key,会导致redis阻塞、崩溃,应用程序异常的情况。

但在删除key的时候,要避免keys命令的使用,这个常识已深入人心。

RedisTemplate 已经封装了关于 set/hash/sorted set 的scan

       redisTemplate.opsForHash().scan();
        redisTemplate.opsForSet().scan();
        redisTemplate.opsForZSet().scan();

下面介绍使用scan配合lua脚本进行删除。

lua 脚本
local function scan(key)
    local cursor = 0
    local keynum = 0

    repeat
        local res = redis.call("scan", cursor, "match", key,'COUNT',ARGV[1])

        if (res ~= nil and #res >= 0) then
            redis.replicate_commands()
            cursor = tonumber(res[1])
            local ks = res[2]
            keynum = #ks
            for i=1,keynum,1 do
                local k = tostring(ks[i])
                redis.call("del", k)
            end
        end
    until (cursor <= 0)

    return keynum
end

local a = #KEYS
local b = 1
local total = 0
while (b <= a)
do
    total = total + scan(KEYS[b])
    b = b + 1
end

return total

1、key [key ...]: 表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。

2、arg [arg ...]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。

springboot 中使用RedisTemplate执行脚本,测试通过
String del = "local function scan(key)\n" +
                "    local cursor = 0\n" +
                "    local keynum = 0\n" +
                "\n" +
                "    repeat\n" +
                "        local res = redis.call(\"scan\", cursor, \"match\", key,'COUNT',ARGV[1])\n" +
                "\n" +
                "        if (res ~= nil and #res >= 0) then\n" +
                "            redis.replicate_commands()\n" +
                "            cursor = tonumber(res[1])\n" +
                "            local ks = res[2]\n" +
                "            keynum = #ks\n" +
                "            for i=1,keynum,1 do\n" +
                "                local k = tostring(ks[i])\n" +
                "                redis.call(\"del\", k)\n" +
                "            end\n" +
                "        end\n" +
                "    until (cursor <= 0)\n" +
                "\n" +
                "    return keynum\n" +
                "end\n" +
                "\n" +
                "local a = #KEYS\n" +
                "local b = 1\n" +
                "local total = 0\n" +
                "while (b <= a)\n" +
                "do\n" +
                "    total = total + scan(KEYS[b])\n" +
                "    b = b + 1\n" +
                "end\n" +
                "\n" +
                "return total";
        System.out.println(del);
        //KEY[1]  为要删除的前缀  举例为 Test.* 代表以Test.开头的所有Key
        //ARGV[1] 单次遍历的数量 注意 不是返回的数量,举例 服务器总共Key有10万  设置为20000则需要循环5次才能全部遍历完

        // 指定 lua 脚本,并且指定返回值类型
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(del, Long.class);
        // 参数一:redisScript,参数二:key列表,参数三:arg(可多个)
        Long result = (Long) redisTemplate.execute(redisScript, Collections.singletonList("myServiceCache*"),1000);
        System.out.println(result);
redis 中执行 使用eval命令执行lua脚本
1、redis-cli命令 连接redis
2、使用eval命令

127.0.0.1:6379> eval "local function scan(key)    local cursor = 0    local keynum = 0    repeat        local res = redis.call("scan", cursor, "match", key,'COUNT',ARGV[1])        if (res ~= nil and #res >= 0) then            redis.replicate_commands()            cursor = tonumber(res[1])            local ks = res[2]            keynum = #ks            for i=1,keynum,1 do                local k = tostring(ks[i])                redis.call("del", k)            end        end    until (cursor <= 0)    return keynum end  local a = #KEYS local b = 1 local total = 0 while (b <= a) do    total = total + scan(KEYS[b])    b = b + 1 end return total" 1 loadU* , 1000
Invalid argument(s)
127.0.0.1:6379> 

3、但是出现报错,带解决。有了解的大神,还望不吝赐教。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容