}finally{
jedis.close();
}
return keys;
}
虽然问题成功解决了,但是小黑哥心里还是有点不解。
为什么 keys 指令会导致其他命令执行变慢?
为什么 Keys 指令查询会这么慢?
为什么 Scan 指令就没有问题?
Redis 执行命令的原理
首先我们来看第一个问题,为什么 keys 指令会导致其他命令执行变慢?
回答这个问题,我们首先看下 Redis 客户端执行一条命令的情况:

站在客户端的视角,执行一条命令分为三步:
- 发送命令
- 执行命令
- 返回结果
但是这仅仅客户端自己以为的过程,但是实际上同一时刻,可能存在很多客户端发送命令给 Redis,而 Redis 我们都知道它采用的是单线程模型。
为了处理同一时刻所有的客户端的请求命令,Redis 内部采用了队列的方式,排队执行。

于是客户端执行一条命令实际需要四步:
- 发送命令
- 命令排队
- 执行命令
- 返回结果
由于 Redis 单线程执行命令,只能顺序从队列取出任务开始执行。
只要 3 这个过程执行命令速度过慢,队列其他任务不得不进行等待,这对外部客户端看来,Redis 好像就被阻塞一样,一直得不到响应。
所以使用 Redis 过程切勿执行需要长时间运行的指令,这样可能导致 Redis 阻塞,影响执行其他指令。
KEYS 原理
接下来开始回答第二个问题,为什么 Keys 指令查询会这么慢?
回答这个问题之前,请大家回想一下 Redis 底层存储结构。
这里小黑哥复制之前文章内容,Redis 底层使用字典这种结构,这个结构与 Java HashMap 底层比较类似。

keys命令需要返回所有的符合给定模式 pattern 的 Redis 中键,为了实现这个目的,Redis 不得不遍历字典中 ht[0]哈希表底层数组,这个时间复杂度为 O(N)(N 为 Redis 中 key 所有的数量)。
如果 Redis 中 key 的数量很少,那么这个执行速度还是也会很快。等到 Redis key 的数量慢慢更加,上升到百万、千万、甚至上亿级别,那这个执行速度就会很慢很慢。
下面是小黑哥本地做的一次实验,使用 lua 脚本往 Redis 中增加 10W 个 key,然后使用 keys 查询所有键,这个查询大概会阻塞十几秒的时间。
eval “for i=1,100000 do redis.call(‘set’,i,i+1) end” 0
这里小黑哥使用 Docker 部署 Redis,性能可能会稍差。
SCAN 原理
最后我们来看下第三个问题,为什么 scan 指令就没有问题?
这是因为 scan命令采用一种黑科技-基于游标的迭代器。
每次调用 scan 命令,Redis 都会向用户返回一个新的游标以及一定数量的 key。下次再想继续获取剩余的 key,需要将这个游标传入 scan 命令, 以此来延续之前的迭代过程。
简单来讲,scan 命令使用分页查询 redis 。
下面是一个 scan 命令的迭代过程示例:
scan 命令使用游标这种方式,巧妙将一次全量查询拆分成多次,降低查询复杂度。
虽然 scan 命令时间复杂度与 keys一样,都是 O(N),但是由于 scan 命令只需要返回少量的 key,所以执行速度会很快。
最后,虽然scan 命令解决 keys不足,但是同时也引入其他一些缺陷:
- 同一个元素可能会被返回多次,这就需要我们应用程序增加处理重复元素功能。
- 如果一个元素在迭代过程增加到 redis,或者说在迭代过程被删除,那个这个元素会被返回,也可能不会。
以上这些缺陷,在我们开发中需要考虑这种情况。
除了 scan以外,redis 还有其他几个用于增量迭代命令:
sscan:用于迭代当前数据库中的数据库键,用于解决smembers可能产生阻塞问题hscan命令用于迭代哈希键中的键值对,用于解决hgetall可能产生阻塞问题。zscan:命令用于迭代有序集合中的元素(包括元素成员和元素分值),用于产生zrange可能产生阻塞问题。
总结
Redis 使用单线程执行操作命令,所有客户端发送过来命令,Redis 都会现放入队列,然后从队列中顺序取出执行相应的命令。
如果任一任务执行过慢,就会影响队列中其他任务的,这样在外部客户端看来,迟迟拿不到 Redis 的响应,看起来就很阻塞了一样。
所以不要在生产执行 keys、smembers、hgetall、zrange这类可能造成阻塞的指令,如果真需要执行,可以使用相应的scan 命令渐进式遍历,可以有效防止阻塞问题。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。


既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
最后,强调几点:
- 1. 一定要谨慎对待写在简历上的东西,一定要对简历上的东西非常熟悉。因为一般情况下,面试官都是会根据你的简历来问的; 能有一个上得了台面的项目也非常重要,这很可能是面试官会大量发问的地方,所以在面试之前好好回顾一下自己所做的项目;
- 2. 和面试官聊基础知识比如设计模式的使用、多线程的使用等等,可以结合具体的项目场景或者是自己在平时是如何使用的;
- 3. 注意自己开源的Github项目,面试官可能会挖你的Github项目提问;
我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!
以上面试专题的答小编案整理成面试文档了,文档里有答案详解,以及其他一些大厂面试题目。
面试答案



《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
mg-gT4MpYL1-1712990203327)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
本文探讨了Rediskeys指令导致命令执行变慢的原因,主要在于其全量扫描底层哈希表的高时间复杂度。相比之下,Scan命令通过分页和游标机制避免了全量扫描,提供了解决方案。作者还强调了在生产环境中应避免阻塞性操作,提倡使用Scan等渐进式查询方法。
1759

被折叠的 条评论
为什么被折叠?



