keys 命令的用法
keys pattern
查找符合正则匹配的 key 的列表。扫描对象是 Redis 服务中所有的 key,想想都很慢对不对?
同时执行 keys 命令的同时,Redis 进程将被阻塞,无法执行其他命令,假如超过了哨兵的 down-after-milliseconds 配置,还会进行主从切换,切换过程中,如果主节点恢复正常,还可能出现 脑裂 等一系列问题。
所以,生产环境中,建议直接禁用 key s命令。所以,生产环境中,建议直接禁用 key s命令。
Keys 命令的替代方案:
- scan 扫描,避免阻塞
- 将需要统计的数据放入一个 set 中 (但是这样可能出现 Big Key 问题,一般数据量大就不推荐)
Keys 命令在 Redis Cluster 中是怎样执行的
一般来说,keys 命令对于集群节点来说,是不知道路由到哪个节点的,不像 get 命令。在 Java 的 Jedis 客户端的 JedisClusterKeyCommands 类中,我们看到:
public Set<byte[]> keys(byte[] pattern) {
// 在每个节点执行keys命令
Collection<Set<byte[]>> keysPerNode = connection.getClusterCommandExecutor()
.executeCommandOnAllNodes((JedisClusterCommandCallback<Set<byte[]>>) client -> client.keys(pattern))
.resultsAsList();
// 合并成一个整体后返回
Set<byte[]> keys = new HashSet<>();
for (Set<byte[]> keySet : keysPerNode) {
keys.addAll(keySet);
}
return keys;
}
我们看到,Jedis 是通过在每个节点上执行 keys 命令,并将结果合并返回的。
本文既然将 keys 命令的慢,那么他到底有多慢呢?
Keys命令到底有多慢?
这里主要是给大家一个基本的概念,并不是深入剖析。
这是腾讯云上 Redis 集群服务中,慢查询的日志。我们看到,Keys 命令大概执行了 250ms ~ 300ms。
根据节点信息,我们看到,每个节点存储了大约 153w 的 key,占用内存 300M+,平均每个键值对占用内存 0.208KB,合 213 个字节。
根据我的理解,既然 keys 命令返回的是 key 值,而集群中其实有一个结构 slots_to_keys 记录着所有 key 的, 这只与 key 的数量有关,与 Big key 的关系不大。
按照这种猜想,假如此时 Redis 节点占用内存为 3G,且 Key 数量成比例,那么 Keys 命令执行时间因为 3s 左右,这段时间 Redis 节点是阻塞的。
keys 和 scan 命令的区别
keys 命令
当 KEYS 命令被用于处理一个大的数据库时, 又或者 SMEMBERS 命令被用于处理一个大的集合键时,它们会锁定 Redis 库, 可能会阻塞服务器达数秒之久,并增加 redis 的 CPU 占用,该命令的时间复杂度为O(N)。在 Redis 拥有数百万及以上的 key 的时候,这个命令执行的会比较慢,更为致命的是,这个命令会阻塞 Redis 多路复用的 IO 主线程,如果这个线程阻塞,在该命令执行期间,其他的发送向 Redis 服务端的命令,都会被阻塞,在高并发下会导致请求大量堆积进而导致服务雪崩。有些公司在生产环境直接禁用 keys 命令。但是在 Redis 服务器 key 的数量不大的情况下,使用 keys 命令也是没啥问题的。
如下图所示,keys 命令的原理就是扫描整个 Redis 里面所有的 key 数据,然后根据我们的通配的字符串进行模糊查找出来
scan 命令
基于上面的 Keys 命令的缺陷,我们这里再引入一个命令,代替 keys 命令,同样是 O(N)复杂度的 scan 命令,支持通配查找,scan 命令或者其他的 scan 如 sscan ,HSCAN,ZSCAN 命令,可以不用阻塞主线程,并支持游标按批次迭代返回数据,所以是比较理想的选择。(PS:scan 命令就是分段扫描 Redis 库,一部分一部分地来扫描,而不是一次性扫描整个库,而且 scan 命令不阻塞主线程)keys 相比 scan 命令优点是,keys 是一次返回,而 scan 是需要迭代多次返回。
如下图所示,scan 命令的底层逻辑是分批次扫描,比如一次性扫描 3 个(这个值可以让我们自定义配置),依次顺序的扫描,直到扫完整个库为止
scan 的缺点
- 因为是分段获取 key,所以它会多次请求 redis 服务器,这样势必会造成 扫描同样数目的 key,scan 耗时更长。
- 在对键进行增量式迭代的过程中, 键可能会被修改, 所以增量式迭代命令只能对被返回的元素提供有限的保证。所以,在查询间隔中新增的 key,不一定会被返回。
- 另外,因为新增或删除 key 都会改变 redis key 的索引,所以,多次查询也会有重复的元素出现,所以使用 scan 命令,一定需要保证 业务处理可重复执行。
总结
- Keys 命令会锁住整个 Redis 库,会阻塞 Redis 的IO多路复用主线程,可能会引起“服务雪崩”问题,但是并不会返回重复 key。
- Scan 命令是分批次进行查询,不会锁住整个 Redis 库,不阻塞 Redis 的 IO 多路复用主线程。查询同样数量的 key,会比 Keys 命令更耗时。在对键进行增量式迭代的过程中, 键可能会被修改, 所以增量式迭代命令只能对被返回的元素提供有限的保证。因为新增或删除 key 都会改变 redis key 的索引,所以,多次查询也会有重复的元素出现,故而应用程序处理的时候记得做幂等性校验处理
评论区