扫描
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
- 可用时间
- 2.8.0
- 时间复杂度
- 每次调用 O(1)。完全迭代 O(N),包括足够多的命令调用以使游标返回到 0。N 是集合中元素的数量。
- ACL 类别
-
@keyspace
,@read
,@slow
,
SCAN
命令及其密切相关的命令 SSCAN
、HSCAN
和 ZSCAN
用于增量迭代元素集合。
由于这些命令允许增量迭代,每次调用只返回少量元素,因此它们可以在生产环境中使用,而不会像 KEYS
或 SMEMBERS
这样的命令那样带来负面影响,这些命令在针对大型键或元素集合调用时可能会阻塞服务器很长时间(甚至几秒钟)。
但是,虽然像 SMEMBERS
这样的阻塞命令能够提供在给定时刻属于集合的所有元素,但 SCAN 命令系列对返回的元素只提供有限的保证,因为我们在增量迭代的集合可以在迭代过程中发生变化。
请注意,SCAN
、SSCAN
、HSCAN
和 ZSCAN
的工作方式非常相似,因此本文档涵盖所有四个命令。但是,一个明显的区别是,在 SSCAN
、HSCAN
和 ZSCAN
的情况下,第一个参数是包含集合、哈希或有序集合值的键的名称。SCAN
命令不需要任何键名参数,因为它迭代当前数据库中的键,因此迭代的对象是数据库本身。
SCAN 基本用法
SCAN 是一个基于游标的迭代器。这意味着在每次调用命令时,服务器都会返回一个更新的游标,用户需要在下次调用时将该游标用作游标参数。
当游标设置为 0 时,迭代开始,当服务器返回的游标为 0 时,迭代结束。以下是 SCAN 迭代的示例
redis 127.0.0.1:6379> scan 0
1) "17"
2) 1) "key:12"
2) "key:8"
3) "key:4"
4) "key:14"
5) "key:16"
6) "key:17"
7) "key:15"
8) "key:10"
9) "key:3"
10) "key:7"
11) "key:1"
redis 127.0.0.1:6379> scan 17
1) "0"
2) 1) "key:5"
2) "key:18"
3) "key:0"
4) "key:2"
5) "key:19"
6) "key:13"
7) "key:6"
8) "key:9"
9) "key:11"
在上面的示例中,第一次调用使用 0 作为游标来启动迭代。第二次调用使用上一次调用返回的游标作为回复的第一个元素,即 17。
如您所见,SCAN 返回值 是一个包含两个值的数组:第一个值是在下次调用中使用的新的游标,第二个值是元素数组。
由于在第二次调用中返回的游标为 0,因此服务器向调用者发出信号,表明迭代已完成,并且集合已完全探索。从游标值为 0 开始迭代,并不断调用 SCAN
直到返回的游标再次为 0,这被称为完全迭代。
返回值
SCAN
、SSCAN
、HSCAN
和 ZSCAN
返回一个包含两个元素的多批量回复,其中第一个元素是一个字符串,表示一个无符号的 64 位数字(游标),第二个元素是一个包含元素数组的多批量。
SCAN
元素数组是一个键列表。SSCAN
元素数组是一个集合成员列表。HSCAN
元素数组包含两个元素,即每个返回的哈希元素的字段和值。ZSCAN
元素数组包含两个元素,即每个返回的有序集合元素的成员及其关联分数。
扫描保证
SCAN
命令以及 SCAN
系列中的其他命令能够为用户提供与完整迭代相关的保证。
- 完整迭代始终检索从完整迭代开始到结束时集合中存在的所有元素。这意味着,如果给定元素在迭代开始时位于集合中,并且在迭代终止时仍然存在,那么
SCAN
在某个时候会将其返回给用户。 - 完整迭代从不返回完整迭代开始到结束时集合中不存在的任何元素。因此,如果某个元素在迭代开始之前被删除,并且在整个迭代期间从未被添加回集合,则
SCAN
确保该元素永远不会被返回。
然而,由于 SCAN
几乎没有关联状态(只有游标),因此它有以下缺点。
- 给定元素可能会被多次返回。由应用程序处理重复元素的情况,例如,仅使用返回的元素来执行在多次重新应用时是安全的的操作。
- 在完整迭代期间并非一直存在于集合中的元素可能会被返回或不返回:这是未定义的。
每次 SCAN 调用返回的元素数量
SCAN
系列函数不能保证每次调用返回的元素数量在给定范围内。这些命令也被允许返回零个元素,并且只要返回的游标不为零,客户端就不应认为迭代已完成。
但是,返回的元素数量是合理的,也就是说,实际上,SCAN
可能会在迭代大型集合时返回最多几十个元素,或者在迭代的集合足够小以至于在内部表示为编码数据结构时(这种情况发生在小型集合、哈希和有序集合中)可能会在单个调用中返回集合的所有元素。
但是,用户可以通过使用 **COUNT** 选项来调整每次调用返回的元素数量的量级。
COUNT 选项
虽然 SCAN
无法保证每次迭代返回的元素数量,但可以使用 **COUNT** 选项来凭经验调整 SCAN
的行为。基本上,使用 COUNT,用户指定了 *每次调用为了从集合中检索元素而应完成的工作量*。这只是对实现的一个 **提示**,但通常来说,这是您大多数时候可以从实现中预期的。
- 默认的
COUNT
值为 10。 - 当迭代键空间或足够大以至于由哈希表表示的集合、哈希或有序集合时,假设没有使用 **MATCH** 选项,服务器通常会每次调用返回 *count* 或比 *count* 多几个元素。请查看本文档后面关于 *为什么 SCAN 可能会一次性返回所有元素* 部分。
- 当迭代编码为 intsets 的集合(仅由整数组成的小集合)或编码为 ziplists 的哈希和有序集合(由小单个值组成的小哈希和集合)时,通常无论
COUNT
值如何,所有元素都会在第一次SCAN
调用中返回。
重要提示:**无需在每次迭代中使用相同的 COUNT 值**。只要在下一个调用中传递的游标是在对命令的先前调用中获得的,调用者就可以根据需要在一次迭代到另一次迭代之间更改计数。
MATCH 选项
可以仅迭代与给定 glob 样式模式匹配的元素,类似于 KEYS
命令的行为,该命令将模式作为其唯一参数。
为此,只需在 SCAN
命令的末尾追加 MATCH <pattern>
参数(它适用于所有 SCAN
系列命令)。
这是一个使用 **MATCH** 进行迭代的示例。
redis 127.0.0.1:6379> sadd myset 1 2 3 foo foobar feelsgood
(integer) 6
redis 127.0.0.1:6379> sscan myset 0 match f*
1) "0"
2) 1) "foo"
2) "feelsgood"
3) "foobar"
redis 127.0.0.1:6379>
需要注意的是,**MATCH** 过滤器是在从集合中检索元素后应用的,就在将数据返回给客户端之前。这意味着,如果模式与集合中的很少元素匹配,SCAN
可能会在大多数迭代中不返回任何元素。下面是一个示例。
redis 127.0.0.1:6379> scan 0 MATCH *11*
1) "288"
2) 1) "key:911"
redis 127.0.0.1:6379> scan 288 MATCH *11*
1) "224"
2) (empty list or set)
redis 127.0.0.1:6379> scan 224 MATCH *11*
1) "80"
2) (empty list or set)
redis 127.0.0.1:6379> scan 80 MATCH *11*
1) "176"
2) (empty list or set)
redis 127.0.0.1:6379> scan 176 MATCH *11* COUNT 1000
1) "0"
2) 1) "key:611"
2) "key:711"
3) "key:118"
4) "key:117"
5) "key:311"
6) "key:112"
7) "key:111"
8) "key:110"
9) "key:113"
10) "key:211"
11) "key:411"
12) "key:115"
13) "key:116"
14) "key:114"
15) "key:119"
16) "key:811"
17) "key:511"
18) "key:11"
redis 127.0.0.1:6379>
正如您所见,大多数调用都返回了零个元素,但最后一个调用使用了一个 COUNT
值为 1000 的值,以强制命令对该迭代进行更多扫描。
当使用 Redis 集群 时,搜索针对暗示单个槽位的模式进行了优化。如果模式只能与一个槽位的键匹配,那么 Redis 只会遍历该槽位的键,而不是遍历整个数据库,来搜索与模式匹配的键。例如,使用模式 {a}h*llo
,Redis 将只会尝试将其与槽位 15495 中的键进行匹配,该槽位暗示了 {a}
哈希标签。要使用带哈希标签的模式,请查看 集群规范中的哈希标签 以获取更多信息。
TYPE 选项
您可以使用 TYPE
选项来要求 SCAN
仅返回与给定 type
匹配的对象,从而允许您遍历数据库以查找特定类型的键。**TYPE** 选项仅适用于整个数据库 SCAN
,不适用于 HSCAN
或 ZSCAN
等。
type
参数与 TYPE
命令返回的相同字符串名称相同。请注意,某些 Redis 类型(例如 GeoHashes、HyperLogLogs、Bitmaps 和 Bitfields)可能在内部使用其他 Redis 类型(例如字符串或 zset)实现,因此无法通过 SCAN
与相同类型的其他键区分开来。例如,ZSET 和 GEOHASH。
redis 127.0.0.1:6379> GEOADD geokey 0 0 value
(integer) 1
redis 127.0.0.1:6379> ZADD zkey 1000 value
(integer) 1
redis 127.0.0.1:6379> TYPE geokey
zset
redis 127.0.0.1:6379> TYPE zkey
zset
redis 127.0.0.1:6379> SCAN 0 TYPE zset
1) "0"
2) 1) "geokey"
2) "zkey"
需要注意的是,**TYPE** 过滤器也在从数据库中检索元素后应用,因此该选项不会减少服务器完成完整迭代所需的工作量,并且对于罕见的类型,您可能会在许多迭代中收到不返回任何元素的情况。
NOVALUES 选项
当使用 HSCAN
时,您可以使用 NOVALUES
选项来使 Redis 仅返回哈希表中的键,而不返回其对应的值。
redis 127.0.0.1:6379> HSET myhash a 1 b 2
OK
redis 127.0.0.1:6379> HSCAN myhash 0
1) "0"
2) 1) "a"
2) "1"
3) "b"
4) "2"
redis 127.0.0.1:6379> HSCAN myhash 0 NOVALUES
1) "0"
2) 1) "a"
2) "b"
多个并行迭代
无限数量的客户端可以在同一时间迭代同一个集合,因为迭代器的完整状态都在游标中,该游标在每次调用时都会获得并返回给客户端。服务器端根本不会保存任何状态。
在中间终止迭代
由于服务器端没有状态,但完整状态由游标捕获,因此调用者可以自由地在中途终止迭代,而无需以任何方式向服务器发出信号。可以启动无限数量的迭代,并且不会在没有问题的情况下终止。
使用损坏的游标调用 SCAN
使用损坏的、负的、超出范围的或其他无效的游标调用 SCAN
将会导致未定义的行为,但不会导致崩溃。未定义的是 SCAN
实现不再能够确保有关返回元素的保证。
唯一有效的游标是
- 开始迭代时的游标值为 0。
- 先前对 SCAN 的调用返回的游标,以便继续迭代。
终止保证
SCAN
算法保证仅在迭代集合的大小保持在给定最大大小范围内时才会终止,否则,迭代始终增长的集合可能会导致 SCAN
永远不会终止完整迭代。
这很容易从直觉上理解:如果集合增长,那么为了访问所有可能的元素,需要做的事情就越来越多,并且能够终止迭代取决于对 SCAN
的调用次数及其 COUNT 选项值与集合增长速度的比较。
为什么 SCAN 可能会在一次调用中返回聚合数据类型的全部项?
在 COUNT
选项文档中,我们说明,有时,这组命令可能会在一次调用中一次性返回集合、哈希或有序集合的所有元素,而无论 COUNT
选项值如何。之所以发生这种情况是因为当我们要扫描的聚合数据类型表示为哈希表时,基于游标的迭代器可以实现,并且是有用的。但是,Redis 使用 内存优化,其中小型聚合数据类型(直到它们达到给定数量的项或给定的单个元素的最大大小)使用紧凑的单分配打包编码表示。在这种情况下,SCAN
没有有意义的游标要返回,并且必须一次性迭代整个数据结构,因此它唯一合理的行为就是在一个调用中返回所有内容。
但是,一旦数据结构变大并且被提升为使用真正的哈希表,SCAN
系列命令将恢复到正常行为。请注意,由于这种返回所有元素的特殊行为仅适用于小型聚合,因此它不会影响命令的复杂性或延迟。但是,转换为真正的哈希表的准确限制是 用户可配置的,因此您在单个调用中看到返回的最大元素数量取决于聚合数据类型可以有多大并且仍然使用打包表示。
还要注意,这种行为是 SSCAN
、HSCAN
和 ZSCAN
特有的。SCAN
本身从不表现出这种行为,因为键空间始终由哈希表表示。
进一步阅读
有关管理键的更多信息,请参阅 Redis 键空间 教程。
其他示例
哈希值的迭代。
redis 127.0.0.1:6379> hmset hash name Jack age 33
OK
redis 127.0.0.1:6379> hscan hash 0
1) "0"
2) 1) "name"
2) "Jack"
3) "age"
4) "33"
RESP2/RESP3 回复
数组回复:具体来说,是一个包含两个元素的数组。
历史
- 从 Redis 6.0.0 版本开始:添加了
TYPE
子命令。