Skip to content

Latest commit

 

History

History
236 lines (160 loc) · 10.6 KB

redis-scan-and-keys.md

File metadata and controls

236 lines (160 loc) · 10.6 KB

Redis SCAN与KEYS

Redis中要遍历全部的key,一般有两个命令:KEYSSCAN

KEYS pattern

KEYS会查找所有符合给定模式pattern的key,基本上是glob风格:

  • KEYS * 匹配数据库中所有key 。
  • KEYS h?llo 匹配hello,hallo和hxllo等。
  • KEYS h*llo 匹配hllo和heeello等。
  • KEYS h[ae]llo 匹配hello和hallo,但不匹配hillo。

特殊符号用\隔开。

时间复杂度是O(N),返回给定模式的key列表。

代码示例:

redis> MSET one 1 two 2 three 3 four 4  # 一次设置 4 个 key
OK

redis> KEYS *o*
1) "four"
2) "two"
3) "one"

redis> KEYS t??
1) "two"

redis> KEYS t[w]*
1) "two"

redis> KEYS *  # 匹配数据库内所有 key
1) "four"
2) "three"
3) "two"
4) "one"

根据文档:

KEYS的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,如果你需要从一个数据集中查找特定的key ,你最好还是用Redis的集合结构(set)来代替。

KEYS操作可能会耗时很久,由于Redis是单线程的,所以KEYS会导致大量的请求被阻塞,在线上使用KEYS是很危险的行为,建议使用SCAN命令来代替KEYSSMEMBERS等命令。

SCAN cursor [MATCH pattern] [COUNT count]

SCAN还有几个相关的命令:SSCAN命令、 HSCAN命令和ZSCAN命令:

  • SCAN命令用于迭代当前数据库中的数据库键。
  • SSCAN命令用于迭代集合键中的元素。
  • HSCAN命令用于迭代哈希键中的键值对。
  • ZSCAN命令用于迭代有序集合中的元素(包括元素成员和元素分值)。

以上列出的四个命令都支持增量式迭代,它们每次执行都只会返回少量元素,所以这些命令可以用于生产环境,而不会出现像KEYS命令、SMEMBERS命令带来的问题 —— 当KEYS命令被用于处理一个大的数据库时,又或者SMEMBERS命令被用于处理一个大的集合键时,它们可能会阻塞服务器达数秒之久。

语法

四个命令的工作方式都非常相似,只有少许不同:

  • SSCAN命令、 HSCAN命令和ZSCAN命令的第一个参数总是一个数据库键。

  • SCAN命令则不需要在第一个参数提供任何数据库键 —— 因为它迭代的是当前数据库中的所有数据库键。

SCAN命令是一个基于游标的迭代器,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命令的回复是一个包含两个元素的数组,第一个数组元素是用于进行下一次迭代的新游标,而第二个数组元素则是一个数组,这个数组中包含了所有被迭代的元素。

在第二次调用SCAN命令时,命令返回了游标0,这表示迭代已经结束,整个数据集已经被完整遍历过了。

以0作为游标开始一次新的迭代,一直调用SCAN命令,直到命令返回游标0,我们称这个过程为一次完整遍历。

有限的保证

SCAN命令, 以及其他增量式迭代命令,在进行完整遍历的情况下可以为用户带来以下保证: 从完整遍历开始直到完整遍历结束期间,一直存在于数据集内的所有元素都会被完整遍历返回; 这意味着, 如果有一个元素,它从遍历开始直到遍历结束期间都存在于被遍历的数据集当中,那么 SCAN命令总会在某次迭代中将这个元素返回给用户。

然而因为增量式命令仅仅使用游标来记录迭代状态,所以这些命令带有以下缺点:

  • 同一个元素可能会被返回多次。 处理重复元素的工作交由应用程序负责,比如说,可以考虑将迭代返回的元素仅仅用于可以安全地重复执行多次的操作上。
  • 如果一个元素是在迭代过程中被添加到数据集的,又或者是在迭代过程中从数据集中被删除的, 那么这个元素可能会被返回,也可能不会。

COUNT选项

增量式迭代命令并不保证每次执行都返回某个给定数量的元素。

增量式命令甚至可能会返回0个元素,但只要命令返回的游标不是0,应用程序就不应该将迭代视作结束。

不过命令返回的元素数量总是符合一定规则的,在实际中:

  • 对于一个大数据集来说, 增量式迭代命令每次最多可能会返回数十个元素;
  • 而对于一个足够小的数据集来说, 如果这个数据集的底层表示为编码数据结构(encoded data structure,适用于是小集合键、小哈希键和小有序集合键), 那么增量迭代命令将在一次调用中返回数据集中的所有元素。

用户可以通过增量式迭代命令提供的COUNT选项来指定每次迭代返回元素的最大值。

基本上,COUNT选项的作用就是让用户告知迭代命令,在每次迭代中应该从数据集里返回多少元素。

虽然COUNT选项只是对增量式迭代命令的一种提示(hint),但是在大多数情况下,这种提示都是有效的。

  • COUNT参数的默认值为10 。
  • 在迭代一个足够大的、由哈希表实现的数据库、集合键、哈希键或者有序集合键时,如果用户没有使用 MATCH 选项, 那么命令返回的元素数量通常和 COUNT 选项指定的一样, 或者比 COUNT 选项指定的数量稍多一些。
  • 在迭代一个编码为整数集合、 或者编码为压缩列表(ziplist,由不同值构成的一个小哈希或者一个小有序集合)时, 增量式迭代命令通常会无视 COUNT 选项指定的值, 在第一次迭代就将数据集包含的所有元素都返回给用户。

并非每次迭代都要使用相同的COUNT值。用户可以在每次迭代中按自己的需要随意改变COUNT值,只要记得将上次迭代返回的游标用到下次迭代里面就可以了。

MATCH选项

和 KEYS 命令一样,增量式迭代命令也可以通过提供一个glob风格的模式参数,让命令只返回和给定模式相匹配的元素,这一点可以通过在执行增量式迭代命令时,通过给定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> 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"

并发执行多个迭代

在同一时间,可以有任意多个客户端对同一数据集进行迭代,客户端每次执行迭代都需要传入一个游标,并在迭代执行之后获得一个新的游标,而这个游标就包含了迭代的所有状态,因此,服务器无须为迭代记录任何状态。

中途停止迭代

因为迭代的所有状态都保存在游标里面,而服务器无须为迭代保存任何状态,所以客户端可以在中途停止一个迭代,而无须对服务器进行任何通知。

即使有任意数量的迭代在中途停止,也不会产生任何问题。

使用错误的游标进行增量式迭代

使用间断的、负数、超出范围或者其他非正常的游标来执行增量式迭代并不会造成服务器崩溃,但可能会让命令产生未定义的行为。

未定义行为指的是,增量式命令对返回值所做的保证可能会不再为真。

只有两种游标是合法的:

  • 在开始一个新的迭代时,游标必须为 0 。
  • 增量式迭代命令在执行之后返回的,用于延续(continue)迭代过程的游标。

迭代终结的保证

增量式迭代命令所使用的算法只保证在数据集的大小有界(bounded)的情况下,迭代才会停止,换句话说,如果被迭代数据集的大小不断地增长的话,增量式迭代命令可能永远也无法完成一次完整迭代。

从直觉上可以看出,当一个数据集不断地变大时,想要访问这个数据集中的所有元素就需要做越来越多的工作,能否结束一个迭代取决于用户执行迭代的速度是否比数据集增长的速度更快。

返回值

SCAN命令、SSCAN命令、HSCAN命令和ZSCAN命令都返回一个包含两个元素的 multi-bulk 回复: 回复的第一个元素是字符串表示的无符号64位整数(游标),回复的第二个元素是另一个multi-bulk回复,包含了本次被迭代的元素。

SCAN命令返回的每个元素都是一个数据库键。

SSCAN命令返回的每个元素都是一个集合成员。

HSCAN命令返回的每个元素都是一个键值对,一个键值对由一个键和一个值组成。

ZSCAN命令返回的每个元素都是一个有序集合元素,一个有序集合元素由一个成员(member)和一个分值(score)组成。