https://redis.io/commands/scan/
一、场景
背景:keys 可以一次性根据 pattern 查出匹配的 key,但因数据量太大会有性能抖动
以是可以用 scan。
scan 的焦点是用游标遍历,避免性能抖动。
二、SCAN 下令
scan 是基于游标的迭代器,每次调用后都会返回新的游标,用户在下次调用时传入新的游标。
当游标设置为 0 时开始迭代,当服务器返回的游标为 0 时终止迭代。
时间复杂度:每次调用 O(1)。完整迭代是 O(N)。
2.1 下令的用法
scan 的语法如下:
- SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
复制代码
- cursor 是一个数字,如 0 为迭代的开始或结束,其他值为中心过程的某游标。
- pattern 是一个通配符,匹配 key,例如 “user*” 即为以 user 开头的字符串,例如 a*b 即为另一种模式的字符串
- count 是一个数字,并不保证精确,基本上表现 redis server 会把游标迭代反复。(这只是实现的一个提示,大多数情况这种提示都是有用的,但数据量少的情况下并不精确)
- type 是 key 的类型,如可以指定 string、set、zset、hash 等
和 scan 类似的,还有如下具体类型的下令(这些具体类型下令的就没有 TYPE 参数了):
- sscan:只迭代 set 类型的元素 https://redis.io/commands/sscan/
- hscan:只迭代 hash 类型的元素 https://redis.io/commands/hscan/
- zscan:只迭代 sorted set 类型的元素 https://redis.io/commands/zscan/
2.2 案例
2.2.1 准备数据
- set str1 a
- sadd set2 a b c
- smembers set2 # b a c (存储时就并不保证顺序)
- set str3 a
- zadd zset4 1 a 2 b 3 c
- zrange zset4 0 -1 # a b c (按各元素的score的顺序,存储和输出)
- hset hash5 a 1 b 2 c 3
- hgetall # a 1 b 2 c 3
- set str6 a
- set str7 a
- set str8 a
- set str9 a
- set str10 a
- set str11 a
- set str12 a
- set str13 a
- set str14 a
复制代码 2.2.2 keys *
- keys * # 可看到工 14 个 keys
- 1) "str1"
- 2) "str12"
- 3) "zset4"
- 4) "str3"
- 5) "str6"
- 6) "str9"
- 7) "str14"
- 8) "str8"
- 9) "str7"
- 10) "str13"
- 11) "str10"
- 12) "set2"
- 13) "str11"
- 14) "hash5"
复制代码 2.2.3 scan
2.2.3.1 仅使用 cursor 迭代
- # 刚开始迭代时,从游标 0 开始
- scan 0
- 1) "7" # 返回游了标 7,说明迭代还没完成。并返回了 11 个元素
- 2) 1) "str14"
- 2) "str8"
- 3) "str12"
- 4) "str7"
- 5) "zset4"
- 6) "str11"
- 7) "hash5"
- 8) "str1"
- 9) "set2"
- 10) "str13"
- 11) "str10"
-
-
- # 因为上次 scan 返回了游标 7,所以这次接着从 7 继续
- scan 7
- 1) "0" # 返回了游标 0,说明迭代完成。并返回了剩余的 3 个元素
- 2) 1) "str3"
- 2) "str6"
- 3) "str9"
复制代码 2.2.3.2 使用 cursor 和 type 迭代
迭代 string type
- # 刚开始迭代时,从游标 0 开始
- scan 0 type string
- 1) "7" # 返回游了标 7,说明迭代还没完成。并返回了 8 个元素
- 2) 1) "str14"
- 2) "str8"
- 3) "str12"
- 4) "str7"
- 5) "str11"
- 6) "str1"
- 7) "str13"
- 8) "str10"
-
- # 因为上次 scan 返回了游标 7,所以这次接着从 7 继续
- scan 7 type string
- 1) "0" # 返回了游标 0,说明迭代完成。并返回了剩余的 3 个元素
- 2) 1) "str3"
- 2) "str6"
- 3) "str9"
- scan 0 type string
- 1) "7"
- 2) 1) "str14"
- 2) "str8"
- 3) "str12"
- 4) "str7"
- 5) "str11"
- 6) "str1"
- 7) "str13"
- 8) "str10"
复制代码 迭代 set type
- scan 0 type set
- 1) "7"
- 2) 1) "set2"
- scan 7 type set
- 1) "0"
- 2) null
复制代码 迭代 zset type
- scan 0 type zset
- 1) "7"
- 2) 1) "zset4"
- scan 7 type zset
- 1) "0"
- 2) null
复制代码 迭代 hash type
- scan 0 type hash
- 1) "7"
- 2) 1) "hash5"
- scan 7 type hash
- 1) "0"
- 2) null
复制代码 2.2.3.3 使用 cursor 和 match 迭代
- scan 0 MATCH str*
- 1) "7"
- 2) 1) "str14"
- 2) "str8"
- 3) "str12"
- 4) "str7"
- 5) "str11"
- 6) "str1"
- 7) "str13"
- 8) "str10"
-
- scan 7 match str*
- 1) "0"
- 2) 1) "str3"
- 2) "str6"
- 3) "str9"
复制代码 2.2.3.4 使用 cursor 和 count 迭代
- scan 0 count 5
- 1) "14"
- 2) 1) "str14"
- 2) "str8"
- 3) "str12"
- 4) "str7"
- 5) "zset4"
-
- scan 14 count 5
- 1) "7"
- 2) 1) "str11"
- 2) "hash5"
- 3) "str1"
- 4) "set2"
- 5) "str13"
- 6) "str10"
-
- scan 7 count 5
- 1) "0"
- 2) 1) "str3"
- 2) "str6"
- 3) "str9"
复制代码 2.2.3.5 使用 cursor、match、count、type 迭代
- # 先查看一下,确实 11 个 str* 的 key
- keys * str*
- 1) "str1"
- 2) "str12"
- 3) "str3"
- 4) "str6"
- 5) "str9"
- 6) "str14"
- 7) "str8"
- 8) "str7"
- 9) "str13"
- 10) "str10"
- 11) "str11"
- # 然后开始用 scan 迭代
- scan 0 match str* count 5 type string
- 1) "14"
- 2) 1) "str14"
- 2) "str8"
- 3) "str12"
- 4) "str7"
-
- scan 14 match str* count 5 type string
- 1) "7"
- 2) 1) "str11"
- 2) "str1"
- 3) "str13"
- 4) "str10"
-
- scan 7 match str* count 5 type string
- 1) "0"
- 2) 1) "str3"
- 2) "str6"
- 3) "str9"
-
- # 最终 scan 确实也返回了 11 个元素,和 keys* 保持了一致
复制代码 2.3 下令阐明
https://redis.io/commands/scan/
scan、sscan、hscan、zscan 都是同类下令,在这里一同介绍。
由于这些下令允许增量迭代,每次调用只返回少量元素,因此它们可以在生产中使用,而不会像KEYS或SMEMBERS这样的下令那样在对大的键或元素聚集调用时会阻塞服务器很长时间(以致几秒钟)。
然而,虽然像SMEMBERS这样的阻塞下令可以或许在给定时候提供属于Set的所有元素,但是SCAN下令家属只提供了对返回元素的有限保证,由于我们递增地遍历的聚集在迭代过程中可能会发生变革。
请注意,SCAN、SSCAN、HSCAN和ZSCAN的工作原理非常相似,因此本文档涵盖了所有四个下令。然而,一个明显的区别是,在SSCAN、HSCAN和ZSCAN的情况下,第一个参数是保存聚集、哈希或排序集值的键的名称。scan 下令不需要任何键名称参数,由于它迭代当前数据库中的键,因此迭代的对象就是数据库自己。
2.3.1 基本使用
SCAN是一个基于游标的迭代器。这意味着在每次调用下令时,服务器都会返回一个更新的游标,用户需要在下一次调用中将其用作游标参数。
迭代在游标设置为0时开始,在服务器返回的游标为0时终止。以下是扫描迭代的示例:
- 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"
复制代码 在上面的例子中,第一个调用使用零作为游标,以开始迭代。第二个调用使用前一个调用返回的游标作为回复的第一个元素,即17。
如上所示,SCAN返回值是一个由两个值组成的数组:第一个值是下一个调用中使用的新游标,第二个值是一个元素数组。
由于在第二次调用中返回的游标是0,以是服务器向调用者发出信号,表现迭代已完成,聚集已完全被探索。用游标值0启动迭代,并调用SCAN直到返回的游标再次为0,称为完整迭代。
2.3.2 返回值
SCAN、SSCAN、HSCAN和 ZSCAN返回两个元素的多批量应答,此中第一个元素是代表无符号64位数字(游标)的字符串,第二个元素是一个元素数组的多批量应答。
- SCAN array of elements is a list of keys.
- SSCAN array of elements is a list of Set members.
- HSCAN array of elements contains two elements, a field and a value, for every returned element of the Hash.
- ZSCAN array of elements contains two elements, a member and its associated score, for every returned element of the Sorted Set.
2.3.3 SCAN 的保证和缺点
SCAN下令和SCAN家属中的其他下令可以或许向用户提供与全迭代相关联的一组保证。
- 完整的迭代,始终检索从完整迭代开始,到结束时聚会合存在的所有元素。这意味着如果一个给定的元素在迭代开始时在聚会合,并且在迭代终止时仍然在那里,那么在某个时候,Scan会将它返回给用户。
- 完整的迭代,不返回从完整迭代开始,到结束时聚会合不存在的任何元素。因此,如果元素在迭代开始之前被移除,并且在迭代持续的所偶然间内都不会被添加回聚集,扫描将确保该元素永世不会被返回。
然而,由于SCAN只有很少的状态关联(只有游标),它有以下缺点:
- 一个给定的元素可能会多次返回。由应用步伐来处理重复元素的情况。例如,只使用返回的元向来执行在多次重新应用时是安全的操作。
- 在一个完整的迭代过程中,聚会合没有经常出现的元素,可以返回也可以不返回:它是未界说的。
2.3.4 每次SCAN调用返回的元素数
scan 族函数不能保证每次调用返回的元素数在给定范围内。下令还可以返回零个元素,只要返回的游标不为零,客户端就不应认为迭代已完成。
然而,返回的元素数量是合理的,也就是说,实际上,在迭代大型聚集时,扫描可以返回几十个元素的顺序的最大数量的元素,或者当迭代的聚集足够小,可以在内部表现为编码的数据结构时,扫描可以在一次调用中返回聚集的所有元素(这发生在较小的聚集、散列和排序的聚会合)。
用户可以 用 COUNT 参数,来调整每次调用返回元素数量的数量级。
2.3.5 COUNT 参数
虽然SCAN并不保证每次迭代返回的元素数量,但是可以使用 COUNT 参数来根据履历调整SCAN的举动。
基本上,使用Count,用户指定每次调用时应该完成的工作量,以便从聚会合检索元素。
这只是实现的一个提示,但一般来说,这是您在实现中大多数情况下所期望的。
- COUNT 的默认值是 10。
- 当迭代键空间,或一个足够大的集,散列或排序集,如果没有使用MATCH选项,服务器每次调用通常会返回count或多于count元素。(下文会介绍为什么可能返回多于 COUNT 的元素)
- 当迭代编码为intsets的Sets(仅由整数组成的小聚集),或编码为ziplist的散列和排序集(小散列和由小的单个值组成的聚集)时,通常所有元素都会在第一次SCAN调用中返回,而不思量初始值。
紧张提示:不需要对每个迭代使用相同的 COUNT 值。调用者可以根据需要自由地将计数从一个迭代更改为另一个迭代,只要下一次调用中传递的游标是在上一次下令调用中获得的游标即可。
2.3.6 MATCH 参数
可以只迭代与给定的全局样式模式匹配的元素,这类似于KEYS下令将模式作为其唯一参数的举动。
要做到这一点,只需在扫描下令的末端附加Match参数(它实用于所有扫描族下令)。
以下是使用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"
复制代码 需要注意,MATCH filter 是后过滤的(即它是在从 collection 中检索元素之后,且将数据返回给客户端之前,操作的)。这意味着,如果 MATCH 与 collection 中很少的元素匹配,扫描很可能在大多数迭代中不返回任何元素。下面是一个示例:
- 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"
复制代码 如上所示,大多数调用返回零个元素,但末了一个调用使用了1000的前缀,以强制下令为该迭代执行更多的扫描。
当使用 Redis Cluster 时,搜索会针对包含单个插槽的模式进行优化。如果一个模式只能匹配一个插槽中的密钥,那么Redis在搜索匹配模式的密钥时,只会迭代该插槽中的密钥,而不是整个数据库。例如,对于模式 {a}h*llo,Redis只会实验将其与插槽15495中的密钥进行匹配,这是哈希标签{a}所暗示的。要使用带有散列标志的模式,在 https://redis.io/docs/reference/cluster-spec/#hash-tags 的规范中的散列标志可以查看更多信息。
2.3.7 TYPE 参数
您可以使用TYPE选项要求SCAN只返回与给定类型匹配的对象,从而允许您在数据库中遍历查找特定类型的键。TYPE选项仅实用于整个数据库SCAN,而不实用于 HSCAN 或 ZSCAN 等。
type参数与TYPE下令返回的字符串名称相同。注意一些Redis类型,如GeoHases,HyperLogistics,Bitmaps和Bitfield,可能在内部基于其他Redis类型实现,如基于 string 或 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"
复制代码 2.3.8 支持并发迭代
支持并发迭代。
无限多的客户端可以同时迭代相同的聚集,由于迭代器的完整状态在游标中,每次调用时都会获得该游标并将其返回给客户端。根本不采用服务器端状态。
2.3.9 支持在中途终止迭代
由于没有状态服务器端,但是游标捕获了完整的状态,调用者可以自由地中途终止迭代,而无需以任何方式向服务器发送信号。无限次的迭代可以开始,并且永世不会在没有任何题目的情况下终止。
2.3.10 使用破坏的游标调用SCAN
使用中断、否定、超出范围或无效游标调用SCAN,将导致未界说的举动,但不会导致崩溃。没有界说的是,SCAN实现不再能确保返回元素的保证。
可以使用的唯一有用游标是:
- 开始迭代时游标值为0。
- 上次调用Scan以继续迭代时返回的游标。
2.3.11 终止担保
SCAN 算法保证只有在迭代聚集的巨细保持在给定的最大巨细范围内时才会终止,否则迭代总是增长的聚集可能会导致SCAN永世不会终止完整的迭代。
这很容易直观地看到:如果聚集增长,为了访问所有可能的元素,需要做的工作越来越多,并且终止迭代的能力取决于对SCAN的调用次数和它的 COUNT 参数与聚集增长的速率相比。
2.3.12 为什么 scan 可能会在单个调用中返回聚合数据类型的所有项?
在 COUNT 选项文档中,我们指出,偶然这类下令可能会在一次调用中同时返回Set、哈希或Sorted Set的所有元素,而不管NTFS选项的值是多少。发生这种情况的原因是,只有当我们扫描的聚合数据类型被表现为哈希表时,基于游标的迭代器才气被实现,并且是有用的。然而Redis使用了内存优化,此中小的聚合数据类型,直到它们达到给定的项目数量或给定的最大单个元素巨细,使用紧凑的单分配打包编码来表现。在这种情况下,SCAN没有任何有意义的游标可返回,必须立即对整个数据结构进行初始化,以是它唯一的正常举动就是在调用中返回所有内容。
然而,一旦数据结构变得更大,并被提升为使用真正的哈希表,SCAN系列下令将诉诸于正常的举动。请注意,由于返回所有元素的这种特殊举动只实用于较小的聚合,以是它对下令的复杂性或延迟没有影响。然而,转换成真实哈希表简直切限定是用户可配置的,因此在单个调用中可以看到返回的最大元素数量取决于聚合数据类型的巨细,并且仍然使用打包表现。
还请注意,这种举动是SSCAN、HSCAN和 ZSCAN特有的。SCAN自己从未显示出这种举动,由于密钥空间总是由哈希表表现。
2.3.13 更多资料
https://redis.io/docs/manual/keyspace
迭代 hash 的示例:
- 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"
复制代码 2.3.14 更多示例
- SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
复制代码 和 Scan 相关的还有如下:
- SCAN:iterates the set of keys in the currently selected Redis database.
- SSCAN:iterates elements of Sets types.
- HSCAN:iterates fields of Hash types and their associated values.
- ZSCAN:iterates elements of Sorted Set types and their associated scores.
由于 Scan 是迭代式获取数据,不像 KEYS 或 SMEMBERS 可能会使单线程的 Redis 阻塞,以是在生产情况可以放心使用。
参数:由于 Scan 获取的是整个 DB 的各 keys,以是不需要任何参数。
返回值:第一个返回值是 cursor(一个数字),第二个返回值是元素数组。
默认值:默认 COUNT 是 10
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |