赞
踩
redis在2.8版本提供了scan相关命令用来遍历集合中的元素。和keys,smembers命令遍历大集合场景下会阻塞redis一定时间不同,scan命令每次遍历只会返回一定数量集合元素和当前的遍历位置的游标,时间非常短,不会阻塞redis,遍历大集合时对其他业务影响较小。缺点是通过多次调用scan命令遍历一个集合会有数据的一致性的问题,后面的相关相关章节会解释。scan家族相关命令有scan,sscan,hscan和zscan,区别如下:
下面是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命令的返回值形式由两部分组成:
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命令来遍历一个集合的,每次遍历只会返回少量的元素。scan家族命令在做一次完整的集合遍历会提供一下保证:
那么小伙伴看到这里可能会有疑问了,如果某个元素在遍历中被移除了会发生什么事情?莫急,下面就说说scan的缺点:
虽然默认scan返回元素数量默认是10个,但是返回的元素数量仍然是不确定的。在遍历一个大集合时可能会返回即几十个元素,在遍历内部使用编码实现小集合(小的set,hash和sorted set)时会一次返回所有的元素。也可能返回一个空集,但这并不表示遍历完毕,当且仅当返回的游标值为0时才表示遍历完毕。
如果先要固定返回指定数量的元素可以使用count选项
count选项用来指定每次scan应该返回的元素数量,但这是一个暗示(hint)。一般来说count的工作原理如下:
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选项只能用于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并不会报错,那么返回的值是不确定的,因为他定位的位置是不确定的。
scan只用当遍历完整个集合才会结束(cursor=0)。因为redis是单线程的,当执行scan命令时,此时集合的大小确定,redis发现到达集合尾部时,遍历就完成了(即使以后会往这个集合中添加数据)。但是如果集合快速增长,那么可能永远遍历不完,这取决于集合增长的速率和你遍历的速率(可以使用count)
scan命令利用cursor值确定上一次遍历的位置,但是这是基于集合底层数据结构是hashtable(哈希表)实现的。由于redis使用了一些内存优化的策略,在集合元素足够小,足够简单时,使用inset(简单的整数集合),ziplist(压缩列表)存储元素,使用cursor定位元素位置不可实现,所以便一次返回所有元素。当随着集合元素增加时,redis会将集合的底层实现转换为哈希表,这时候count就又生效了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。