理解redis中的scan

基本用法

redis在2.8版本提供了scan相关命令用来遍历集合中的元素。和keys,smembers命令遍历大集合场景下会阻塞redis一定时间不同,scan命令每次遍历只会返回一定数量集合元素和当前的遍历位置的游标,时间非常短,不会阻塞redis,遍历大集合时对其他业务影响较小。缺点是通过多次调用scan命令遍历一个集合会有数据的一致性的问题,后面的相关相关章节会解释。scan家族相关命令有scan,sscan,hscan和zscan,区别如下:

  • scan遍历的是当前数据库的所有key组成的集合
  • sscan用来遍历一个set里的所有元素
  • hscan用来遍历一个哈希表的所有元素,返回的list是key1,field1,key2,field2…的形式
  • zscan用来遍历zset,返回的list中包含对应的元素和它的分数score

下面是scan,sscan,hscan,zscan命令的语法:
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
SSCAN key cursor [MATCH pattern] [COUNT count]
HSCAN key cursor [MATCH pattern] [COUNT count]
ZSCAN key cursor [MATCH pattern] [COUNT count]

cursor:游标,表示遍历开始的位置,当游标为0时表示开始遍历一个集合,返回的游标值为0时表示集合遍历已经遍历完毕。
[MATCH pattern]: 用来筛选匹配元素的正则表达式。
[COUNT count]:可以指定每次扫描返回的元素的数量
[TYPE type]:6.0版本新增的参数,只支持scan命令,表示扫描指定类型的key,key的类型可以用type命令查看。

scan,sscan,hscan,zscan命令的返回值形式由两部分组成:

  • 游标
  • 元素组成的list
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"

scan命令的保证(guarantees)

通过上面的介绍可以知道,我们是通过多次调用scan命令来遍历一个集合的,每次遍历只会返回少量的元素。scan家族命令在做一次完整的集合遍历会提供一下保证:

  • 如果一个元素从头到尾(第一次调用scan到最后一次调用scan)都在一个集合中,那个这个元素一定在某次(也许是几次)scan的调用中被返回。
  • 如果一个元素在第一次scan之前就被移除并且在整个遍历过程中没有被加回到集合中,那么这个元素一定不会被返回。

那么小伙伴看到这里可能会有疑问了,如果某个元素在遍历中被移除了会发生什么事情?莫急,下面就说说scan的缺点:

  • 如果一个元素在比遍历过程中不总是出现在集合中(被移除过),那么这个元素被返回(scan到)是不确定的。
  • 一个元素可能被多次返回,这完全依靠客户端去处理重复元素。

每次scan返回元素的数量

虽然默认scan返回元素数量默认是10个,但是返回的元素数量仍然是不确定的。在遍历一个大集合时可能会返回即几十个元素,在遍历内部使用编码实现小集合(小的set,hash和sorted set)时会一次返回所有的元素。也可能返回一个空集,但这并不表示遍历完毕,当且仅当返回的游标值为0时才表示遍历完毕。
如果先要固定返回指定数量的元素可以使用count选项

count选项

count选项用来指定每次scan应该返回的元素数量,但这是一个暗示(hint)。一般来说count的工作原理如下:

  • count的默认值是10
  • 当扫描key集合,set,hash和zset时,而且集合足够大集合内部的结构是一个哈希表(hash table)时,scan返回的元素数量会等于count值或者比count值大一点。
  • 当set的底层实现是intset整数集合实现,或者hash和zset的底层是ziplist压缩列表实现的时候,不管count值是什么都会返回所有的元素

match选项

match选项用来过滤scan的元素,而且是redis获取本次scan的元素后,再过滤的。也就是说,redis仍然遍历所有的元素,工作量和不指定match一样。假如你指定match= product_id*表示只匹配以product_id开头的元素,redis先从获取到本次scan的元素,在返回客户端之前过滤一遍。这样导致的结果是,可能多次scan的都是空的,但这不意味着遍历完毕了,只是本次遍历的元素中没有以product_id开头的元素,只有返回游标为0时才表示遍历结束。所以,在遍历大集合中的少量特定元素时,多数情况都会返回空集合。如果指定每次都要求返回指定数量的匹配元素,可以使用count,但是这可能阻塞redis,那么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>

type选项

type选项只能用于scan命令,表示遍历特定类型的集合,它也是类似于count选项指定的正则过滤器一样,只不过type这个过滤器过滤特定集合类型。和match选项一样,redis获取到本次遍历的元素后,再对结果集过滤,所以多数情况下都会返回空集合。

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"

并行遍历

redis记录遍历的位置信息,而是将本次遍历信息全部保存在的cursor值中并返回客户端。下一次scan时,redis根据cursor值重新定位到上一次遍历的地方。redis本身不保存遍历的信息。

中断遍历

因为遍历的位置信息保存在cursor值并返回了客户端,redis不保存任何信息,所以客户端可以随时终止遍历,而无需与redis有任何交互。

使用错误的游标值

如果使用了错误的cursor值(不是返回的cursor值),redis并不会报错,那么返回的值是不确定的,因为他定位的位置是不确定的。

遍历完成的保证(Guarantee of termination)

scan只用当遍历完整个集合才会结束(cursor=0)。因为redis是单线程的,当执行scan命令时,此时集合的大小确定,redis发现到达集合尾部时,遍历就完成了(即使以后会往这个集合中添加数据)。但是如果集合快速增长,那么可能永远遍历不完,这取决于集合增长的速率和你遍历的速率(可以使用count)

为什么在遍历聚合数据类型(intset,ziplist…)时会一次返回所有元素

scan命令利用cursor值确定上一次遍历的位置,但是这是基于集合底层数据结构是hashtable(哈希表)实现的。由于redis使用了一些内存优化的策略,在集合元素足够小,足够简单时,使用inset(简单的整数集合),ziplist(压缩列表)存储元素,使用cursor定位元素位置不可实现,所以便一次返回所有元素。当随着集合元素增加时,redis会将集合的底层实现转换为哈希表,这时候count就又生效了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值