在redis实际使用中,会遇到一个问题:如何从海量的key中找出满足特定前缀的key列表?

1.不要使用keys*

redis提供了一个简单包里的指令keys用来列出所有满足特定正则字符串规则的key。

keys xxx*

这个指令有致命的弊端,在实际环境中最好不要使用:

这个指令没有offset、limit参数,是要一次性吐出所有满足条件的key,由于redis是单线程的,其所有操作都是原子的,而 keys 算法是遍历算法,复杂度是 O(n),如果实例中有千万级以上的 key,这个指令就会导致 Redis 服务卡顿,所有读写 Redis 的其它的指令都会被延后甚至会超时报错,可能会引起缓存雪崩甚至数据库宕机。

我们可以通过配置设置禁用这些命令,在 redis.conf 中,在 SECURITY 这一项中,我们新增以下命令:

rename-command flushall ""
rename-command flushdb ""
rename-command config ""
rename-command keys ""

另外,对于FLUSHALL命令,需要设置配置文件中appendonly no,否则服务器是无法启动。

Redis 为了解决这个问题,它在 2.8 版本中加入了scan。scan 相比 keys 具备有以下特点:

  • 复杂度虽然也是 O(n),但是它是通过游标分步进行的,不会阻塞线程;
  • 提供 limit 参数,可以控制每次返回结果的最大条数,limit 只是一个 hint,返回的结果可多可少;
  • 同 keys 一样,它也提供模式匹配功能;
  • 服务器不需要为游标保存状态,游标的唯一状态就是 scan 返回给客户端的游标整数;
  • 返回的结果可能会有重复,需要客户端去重复,这点非常重要;
  • 遍历的过程中如果有数据修改,改动后的数据能不能遍历到是不确定的;
  • 单次返回的结果是空的并不意味着遍历结束,而要看返回的游标值是否为零;

2.scan使用

SCAN 命令及其相关的 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令都用于增量地迭代:

SCAN命令用于迭代当前数据库中的数据库键。

SSCAN命令用于迭代集合键中的元素。

HSCAN命令用于迭代哈希键中的键值对。

ZSCAN命令用于迭代有序集合中的元素(包括元素成员和元素分值)

相比于keys命令,SCAN命令有两个比较明显的优势:

1)SCAN命令的时间复杂度虽然也是O(N),但它是分次进行的,不会阻塞线程。

2)SCAN命令提供了count参数,可以控制每次遍历的集合数。

可以理解为SCAN是渐进式的keys

Scan命令语法如下:
SCAN cursor [MATCH pattern] [COUNT count]
curson-游标
pattern-匹配的模式
count-指定每次遍历多少个集合

SCAN命令是基于游标的,每次调用后,都会返回一个游标,用于下一次迭代。

第一次 Scan 时指定游标为 0,表示开启新的一轮迭代,然后 Scan 命令返回一个新的游标,作为第二次 Scan 时的游标值继续迭代,一直到 Scan 返回游标为0,表示本轮迭代结束。

Scan完成一次迭代,需要和redis进行多次交互。

3.scan参数

Scan 命令中的 Count 指定一次扫描多少 Key,这里指定为 1000,几百万Key就需要几千次迭代,即和 Redis 交互几千次,然后因为是远程连接,网络延迟比较大,所以耗时特别长。

将 Count 参数调大后,减少了交互次数,会好很多。Count 参数越大,Redis 阻塞时间也会越长,需要取舍。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐