redis游标scan那些事儿,聊聊它到底怎么变革和改进的
- 问答
- 2026-01-13 09:56:40
- 2
说到Redis的SCAN命令,咱们得先聊聊它出现之前,大家是怎么找钥匙的,在SCAN诞生之前,如果你想找出所有符合某个模式的键,比如找出所有以“user:session:”开头的会话键,你会用什么命令?对,就是KEYS命令,你可能会直接敲入 KEYS user:session:*,然后Redis就会吭哧吭哧地把所有匹配的键一下子全给你列出来。
听起来很爽快,对吧?但这种方式有一个致命的缺点,用行话叫“阻塞”,咱们用大白话讲就是:它会卡住整个Redis,Redis是单线程处理命令的,这意味着同一时间只能干一件事,当你对一个有几百万甚至上亿个键的Redis实例使用KEYS *这种命令时,Redis就得停下手里所有其他的活(比如处理用户的登录、下单、查询等所有请求),全心全意地去遍历整个钥匙串,这个过程可能需要好几秒甚至更长时间,在这期间,整个Redis服务就相当于“僵住了”,无法响应其他任何操作,这在高并发的生产环境下简直是灾难性的,正因为如此,Redis官方在文档里明确警告:生产环境禁止使用KEYS命令。(来源:Redis官方文档对KEYS命令的警告)
那怎么办呢?总不能因噎废食,不让我们找钥匙了吧?在Redis 2.8版本,一个革命性的改进来了——SCAN命令家族。(来源:Redis 2.8版本发布说明中引入SCAN命令)

SCAN的核心思想非常巧妙,它放弃了“一口吃成胖子”的做法,改用了一种“分批拉取”的方式,它就像是在一本巨大的字典里帮你查单词,但它不一次性把整本字典复印给你,而是每次只翻几页,告诉你这几页里有哪些你要的单词,并且记住当前翻到的位置(这个位置就是“游标”),下次你让它继续,它就从这个位置接着往下翻。
你第一次执行SCAN 0 MATCH user:session:*,这里的0表示从开头开始扫描,Redis会返回两个东西:一个是下一个游标值(比如是12345),另一个是本次扫描得到的少量键的列表(比如10个键),你再用这个新的游标值12345去执行下一次扫描:SCAN 12345 MATCH user:session:*,如此反复,直到Redis返回的下一个游标值是0,这就意味着整个字典已经翻完了,遍历结束。

这种方式带来了几个巨大的好处:
- 非阻塞:这是最关键的改进,每次SCAN操作都只花费非常短的时间(复杂度是O(1)),因为它只扫描一小部分键,即使数据库里有海量数据,单次SCAN也不会导致服务器卡顿,它把一次性的长时阻塞,分解成了多次短暂的、可间歇执行的微操作。
- 可中途停止:你不需要一次性完成整个遍历,你的程序扫描到一半,发现已经找到了足够的数据,或者服务器负载突然升高,你可以轻松地停止后续的扫描,而不会造成任何浪费或问题,而
KEYS命令一旦开始,就只能等它全部执行完。 - 一致性保证:在SCAN遍历的整个过程中,即使数据库中的键发生了增删改(比如有的键过期被删除了,或者新增了键),SCAN命令也能最大限度地保证,只要在遍历开始时存在的键,并且在整个遍历过程中没有被删除,那么它最终都会被返回,它不保证能返回遍历过程中新加入的键,但这也比在遍历过程中因为键空间变化而出现混乱要好得多。(来源:Redis官方文档对SCAN命令一致性的说明)
SCAN也并非完美,它存在重复扫描的可能性,因为SCAN在遍历过程中不锁定整个数据库,所以在它分批扫描的间隙,如果发生了Rehash(Redis内部扩容缩容的一种机制),可能会导致一小部分键被重复扫描到,不过在实际应用中,只要你的客户端逻辑是幂等的(即处理重复数据不会导致错误),这点小瑕疵通常是可以接受的,毕竟用它带来的非阻塞好处是实实在在的。
后来,Redis还基于同样的思想,为哈希表、集合、有序集合等数据类型也提供了HSCAN、SSCAN、ZSCAN命令,让你也能以增量、非阻塞的方式遍历大体积的复杂数据类型,这进一步丰富了SCAN家族的场景适用性。
SCAN命令的引入,是Redis从一个简单的缓存工具向一个能够支撑大规模、高并发业务的成熟数据库系统演进的重要一步,它用“分治”的智慧,巧妙地解决了全量遍历的性能瓶颈问题,让开发者在管理大数据集时有了更安全、更优雅的选择,直到今天,“生产环境用SCAN,禁用KEYS”依然是每个Redis使用者必须牢记的黄金法则。
本文由凤伟才于2026-01-13发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://www.haoid.cn/wenda/79861.html
