9. Redis中游标迭代器(scan)
楔子
我们说如果想查询数据库中都有哪些key的话,那么可以使用keys命令来查看,keys后面接一个模式,即可返回所有匹配指定模式的key。并且指定模式的时候,可以使用通配符,比如:
*:匹配任意多个任意字符
?:匹配单个任意字符
[...]:匹配[]中的任意一个字符
当然keys这个命令很简单,用起来也很方便,但是该命令存在两个缺点:
- 此命令没有分页功能,我们只能一次性查询出所有符合条件的 key 值,如果查询结果非常巨大,那么得到的输出信息也会非常多;
- keys 命令是遍历查询,等于将数据库中的key和指定的模式一一对比,看是否匹配,因此它的查询时间复杂度是 o(n),所以数据量越大查询时间就越长。
并且由于每个Redis实例是使用单线程处理所有请求的,故keys命令和其他命令都是在同一个队列排队等待执行的,如果keys命令执行时间过长,则会阻塞其他命令的执行,导致性能问题。并且如果keys命令需要匹配非常多的key,不仅输出信息多,还可能造成长期停顿。
scan命令
因此为了解决这一点,Redis在2.8版本的时候提出了一个scan命令,主要用于解决keys命令可能导致整个Redis实例停顿的问题。
scan是一种迭代命令,主要是对keys命令进行了分解,即原本使用一个keys请求一次匹配获取所有符合的key的操作,分解了多次scan操作。每次scan操作返回匹配的key的一个子集,这样每个scan请求的操作时间很短,多次scan请求之间可以执行其他命令,故减少对其他命令执行的阻塞。当最后一个scan请求发现没有数据可返回了,则操作完成,汇总该次所有scan请求的数据,从而达到与keys命令一次获取的数据相同。
由于scan命令需要执行多次,即相当于执行了多个命令,存在多次命令请求和响应周期,故整体执行时间可能要比keys命令长。
生成数据
我们使用Python往Redis里面添加25个key。
import redis
client = redis.Redis(host="47.94.174.89", decode_responses="utf-8")
# 生成24个key
keys = [f"satori{i}" for i in range(1, 25)]
values= list(range(1, 25))
# 添加
client.mset(dict(zip(keys, values)))
然后我们看一下scan怎么使用,命令:scan 游标 match 模式 count 数量
,其中的match和count是可选的,我们先来讲一下游标。
127.0.0.1:6379> scan 0 # 我们这里没有指定match,会匹配所以的key;没有指定count,默认每次返回10条
1) "14" # 然后游标以0开始,返回数据之后,会得到一个新的游标,然后下次从这个新的游标开始迭代
2) 1) "satori1"
2) "satori14"
3) "satori12"
4) "satori22"
5) "satori20"
6) "satori11"
7) "satori15"
8) "satori5"
9) "satori21"
10) "satori8"
127.0.0.1:6379> scan 14 # 上一个游标返回了14,所以第二次从14开始迭代,然后返回10条
1) "23" # 返回游标23
2) 1) "satori18"
2) "satori24"
3) "satori23"
4) "satori9"
5) "satori3"
6) "satori13"
7) "satori17"
8) "satori4"
9) "satori10"
10) "satori6"
127.0.0.1:6379> scan 23 # 所以这里从第二次返回的游标23 开始迭代
1) "0" # 如果游标返回了0,代表迭代结束了,此时所以的key都被迭代过了。
2) 1) "satori19"
2) "satori16"
3) "satori7"
4) "satori2"
127.0.0.1:6379>
我们讲解一下里面的参数:
游标:光标位置,从0开始,到0结束。如果游标值返回的不是0的话,尽管查询结果是空,迭代也依旧没有结束。
match 模式:匹配指定模式的key,类似于keys pattern中的pattern。如果不指定那么等价于全部匹配
count 数量:指定返回的数量,如果不指定,那么每次返回10条
实际操作一下,不过这里的key有点多,我们就删除一部分只保留14个key吧。
127.0.0.1:6379> scan 0 count 11 # 返回11个
1) "7"
2) 1) "satori1"
2) "satori14"
3) "satori12"
4) "satori11"
5) "satori5"
6) "satori8"
7) "satori13"
8) "satori3"
9) "satori9"
10) "satori4"
11) "satori10"
127.0.0.1:6379> scan 7 # 还剩4个
1) "0"
2) 1) "satori6"
2) "satori2"
3) "satori7"
127.0.0.1:6379>
127.0.0.1:6379> scan 0 count 15 # 直接返回15个
1) "0" # 光标直接变为0,因为遍历一次就结束了
2) 1) "satori1"
2) "satori14"
3) "satori12"
4) "satori11"
5) "satori5"
6) "satori8"
7) "satori13"
8) "satori3"
9) "satori9"
10) "satori4"
11) "satori10"
12) "satori6"
13) "satori2"
14) "satori7"
127.0.0.1:6379>
匹配模式,这里选择以satori1开头的。
127.0.0.1:6379> scan 0 match satori1* # 但是这里没有遍历完毕,而是保留了一个
1) "11"
2) 1) "satori1"
2) "satori14"
3) "satori12"
4) "satori11"
5) "satori13"
127.0.0.1:6379> scan 11 match satori1* # 所以判断遍历是否结束,我们只看它返回的游标是不是0。
1) "0"
2) 1) "satori10"
使用Python操作Redis中游标
import redis
client = redis.Redis(host="47.94.174.89", decode_responses="utf-8", password=123456)
# 里面三个参数:分别是cursor、match、count后面两个默认为None
print(client.scan(0, count=3)) # (2, ['satori1', 'satori14', 'satori12', 'satori11'])
# 会返回一个元组,里面是游标和对应的key(一个列表)
# 那么我们就可以开始遍历了, 一次遍历6个吧
cursor = 0
while res := client.scan(cursor, count=6):
print(res[1])
if not (cursor := res[0]):
break
"""
['satori1', 'satori14', 'satori12', 'satori11', 'satori5', 'satori8']
['satori13', 'satori3', 'satori9', 'satori4', 'satori10', 'satori6']
['satori2', 'satori7']
"""
以上就是scan命令,除了scan,还有hscan:检索字典中的键值对、sscan:检索集合中的元素、zscan:检索有序集合中的元素(包括成员和分数值)
,有兴趣可以自己尝试一下。这些命令的使用方式都是一样的,只不过命令的名字不一样罢了。
如果觉得文章对您有所帮助,可以请囊中羞涩的作者喝杯柠檬水,万分感谢,愿每一个来到这里的人都生活愉快,幸福美满。
微信赞赏
支付宝赞赏