1. 为什么在海量Key面前,KEYS命令是“危险品”?
很多刚接触Redis的朋友,遇到需要按模式查找Key的场景,第一反应可能就是使用KEYS命令。比如想找出所有以user:session:开头的会话Key,直接在命令行敲一个KEYS user:session:*,结果瞬间就出来了,感觉非常方便。但我要告诉你,在生产环境,这个命令几乎等同于一个“危险品”,尤其是在数据量大的时候。
我自己就踩过这个坑。早期在一个用户量还不大的项目里,我习惯用KEYS来查东西。后来随着业务增长,Redis里的Key膨胀到了几百万级别。有一次在线上执行了一个KEYS *想看看整体情况,结果整个Redis实例卡住了将近2秒!那2秒里,所有依赖这个Redis的实时接口全部超时告警,监控大盘一片飘红,差点酿成线上事故。
为什么KEYS命令这么可怕?原因在于它的工作方式是阻塞式遍历。Redis是单线程模型,同一时间只能处理一个命令。当KEYS命令执行时,它会一次性遍历整个数据库的所有Key(时间复杂度O(n)),直到找出所有匹配项后才返回。在这个过程中,Redis无法处理其他任何请求,就像交通要道被一辆巨型卡车完全堵死一样。如果你的Redis里有上亿个Key,这个阻塞时间可能会长达数秒甚至更久,这对于要求高并发的在线服务来说是致命的。
所以,请务必记住这条铁律:在生产环境中,绝对禁止使用KEYS命令。这不是建议,而是必须遵守的规范。那么,安全的替代方案是什么?这就是我们今天要深入探讨的SCAN命令家族。
2. SCAN命令:你的海量数据“巡洋舰”
如果说KEYS是一辆会堵死所有交通的巨型卡车,那么SCAN就是一艘灵活且不会阻塞航道的巡洋舰。它是Redis在2.8版本中引入的增量式迭代命令,核心设计目标就是解决KEYS命令的阻塞问题。
SCAN的基本用法很简单,但理解其输出是关键。我们来看一个最直接的例子:
127.0.0.1:6379> SCAN 0 MATCH user:token:* COUNT 100
1) "176" # 下一次迭代要使用的游标值
2) 1) "user:token:1001" # 本次迭代返回的Key列表
2) "user:token:1005"
3) "user:token:1008"
这里有几个核心点需要你立刻掌握:
- 游标(Cursor):命令中的
0是起始游标。返回结果的第一部分(例如"176")是下一个游标值。当返回的游标为0时,才表示整个遍历结束。即使某次返回的Key列表是空的,只要游标不是0,你就必须继续。 - MATCH模式:和
KEYS一样,支持通配符匹配。 - COUNT参数:这是最容易误解的地方。
COUNT 100不保证返回100个Key。它只是建议Redis在内部每次迭代时检查大约100个“槽位”(slot)。返回的Key数量可能少于、等于或多于这个数。它只是一个提示(hint),用于平衡单次调用耗时和总调用次数。
SCAN是如何做到不阻塞的呢?秘诀在于分步和游标。它不会一次性遍历所有Key,而是每次只遍历一小部分(受COUNT影响),然后立刻返回结果和一个游标。客户端拿到这个游标,在下次调用时传回给Redis,Redis就能从上次停止的地方继续遍历。这样就把一个长时间的阻塞操作,拆分成许多个短暂的、非阻塞的小操作,期间Redis可以正常处理其他请求。
3. 深入原理:COUNT参数、遍历顺序与重复数据
要真正用好SCAN,不能只停留在表面命令,还得理解它背后的工作原理。这能帮你解释很多看似奇怪的现象,并做出正确的优化。
3.1 COUNT参数到底控制了什么?
我们通过一个实验来直观感受。假设一个数据库有大量Key,我们执行:
127.0.0.1:6379> SCAN 0 MATCH * COUNT 10
1) "15360"
2) (empty list or set)
127.0.0.1:6379> SCAN 15360 MATCH * COUNT 10
1) "2304"
2) (empty list or set)
看到了吗?COUNT设了10,但返回了空列表。这是因为Redis内部存储Key的是一个哈希表(一维数组+二维链表)。COUNT参数指的是单次迭代要扫描的哈希表“槽位”(数组索引位置)数量,而不是直接返回的Key数量。
一个槽位可能为空,也可能挂着一个包含多个Key的链表。SCAN每次迭代,会顺序扫描大约


3万+

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



