Redis SCAN 命令
Redis key(键)](https://www.runoob.com/redis/redis-keys.html "Redis key(键)")
Redis Scan 命令用于迭代数据库中的数据库键。
SCAN 命令是一个基于游标的迭代器,每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。
SCAN 返回一个包含两个元素的数组, 第一个元素是用于进行下一次迭代的新游标, 而第二个元素则是一个数组, 这个数组中包含了所有被迭代的元素。如果新游标返回 0 表示迭代已结束。
相关命令:
语法
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、但是出现报错,带解决。有了解的大神,还望不吝赐教。