12)Redis 的游标迭代器(scan)

简介: 12)Redis 的游标迭代器(scan)

我们知道,如果想查询数据库中都有哪些 key 的话,可以使用 keys 命令。keys 后面接一个模式,即可返回所有匹配指定模式的 key。并且指定模式的时候,可以使用通配符,比如:

  • *:匹配任意多个任意字符;
  • ?:匹配单个任意字符;
  • [...]:匹配[]中的任意一个字符;

keys 这个命令很简单,用起来也很方便,但是该命令存在两个缺点:

1)此命令没有分页功能,我们只能一次性查询出所有符合条件的 key,如果查询结果非常巨大,那么得到的输出信息也会非常多;

2)keys 命令是遍历查询,等于将数据库中的 key 和指定的模式逐一对比,看是否匹配,因此它的查询时间复杂度是 O(N),所以数据量越大查询时间就越长;

并且由于每个 Redis 实例是使用单线程处理所有请求的,故 keys 命令和其它命令都是在同一个队列中排队等待执行的。如果 keys 命令执行时间过长,则会阻塞其它命令的执行,导致性能问题。而且当 keys 命令匹配了非常多的 key 时,不仅输出信息多,还可能造成长期停顿。

因此为了解决这一点,Redis 在 2.8 版本的时候引入了一个 scan 命令,主要用于解决 keys 命令可能导致整个 Redis 实例停顿的问题。

scan 是一种迭代命令,主要是对 keys 命令进行了分解,也就是将原本使用一个 keys 请求一次即可获取所有符合模式的 key 的操作,分解成了多次 scan 操作。

每次 scan 操作返回匹配的 key 的一个子集,这样每个 scan 请求的操作时间很短,多次 scan 请求之间可以执行其他命令,故减少对其他命令执行的阻塞。当最后一个 scan 请求发现没有数据可返回了,则操作完成,汇总所有 scan 请求的数据,从而达到与 keys 命令一次获取的数据相同的效果。

由于 scan 命令需要执行多次,即相当于执行了多个命令,存在多次命令请求和响应周期,故整体执行时间要比 keys 命令长。但它的特点是将整个步骤进行了分解,在这个过程中可以执行其它命令。

我们使用 Python 往 Redis 里面添加一些 key。

import redis
client = redis.Redis(host="...",
                     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 是可选的,我们先来讲一下游标。

# 我们这里没有指定 match,会匹配所有的 key
# 没有指定 count,默认每次遍历 10 条
127.0.0.1:6379> scan 0  
# 然后游标从 0 开始,返回数据之后
# 会得到一个新的游标,然后下次从这个新的游标开始迭代
1) "14"  
2)  1) "satori1"
    2) "satori14"
    3) "satori12"
    4) "satori22"
    5) "satori20"
    6) "satori11"
    7) "satori15"
    8) "satori5"
    9) "satori21"
   10) "satori8"
   
# 上一个游标返回了14,所以第二次从 14 开始迭代
# 然后遍历 10 条   
127.0.0.1:6379> scan 14  
1) "23"  # 返回游标23
2)  1) "satori18"
    2) "satori24"
    3) "satori23"
    4) "satori9"
    5) "satori3"
    6) "satori13"
    7) "satori17"
    8) "satori4"
    9) "satori10"
   10) "satori6"
# 所以这里从第二次返回的游标 23 开始迭代
# 如果游标返回了 0,代表所有的 key 都迭代完毕了
127.0.0.1:6379> scan 23  
1) "0"  
2) 1) "satori19"
   2) "satori16"
   3) "satori7"
   4) "satori2"
127.0.0.1:6379>

所以游标还是很好理解的,至于剩下的 match 和 count 就更不用说了。

  • match 模式:匹配指定模式的 key,类似于 keys pattern 中的 pattern。如果不指定则等价于全部匹配;
  • count 数量:指定遍历的数量,如果不指定,那么每次遍历 10 条;

实际操作一下,不过这里的 key 有点多,我们就删除一部分只保留 14 个 key 吧。

# 返回 11 个
127.0.0.1:6379> scan 0 count 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"
   
# 还剩 4 个   
127.0.0.1:6379> scan 7  
1) "0"
2) 1) "satori6"
   2) "satori2"
   3) "satori7"
# 直接返回 15 个
127.0.0.1:6379> scan 0 count 15  
# 光标直接变为 0,因为遍历一次就结束了
1) "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"

匹配模式,这里选择以 satori1 开头的。

# 返回值不是 0,说明没有遍历完毕
127.0.0.1:6379> scan 0 match satori1*  
1) "11"
2) 1) "satori1"
   2) "satori14"
   3) "satori12"
   4) "satori11"
   5) "satori13"
# 判断遍历是否结束,我们只看它返回的游标是不是 0
# 这里返回了 0,说明遍历结束了
127.0.0.1:6379> scan 11 match satori1*  
1) "0"
2) 1) "satori10"

以上就是 scan 命令的用法,再来看看如何用 Python 操作 scan。

import redis
client = redis.Redis(host="...", 
                     decode_responses="utf-8")
# 里面三个参数:分别是 cursor、match、count
# 后面两个默认为 None
print(client.scan(0, count=3))
"""
(2, ['satori1', 'satori14', 'satori12', 'satori11'])
"""
# 调用 scan 会返回一个元组
# 里面是游标和遍历得到的 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:检索有序集合中的元素(包括成员和分数值),有兴趣可以自己尝试一下。

并且这些命令的使用方式都是一样的,只不过命令的名字不一样罢了。

相关文章
|
12月前
|
缓存 NoSQL 算法
Redis之MoreKey问题及Scan命令解读
Redis之MoreKey问题及Scan命令解读
|
存储 NoSQL 算法
Redis进阶-如何从海量的 key 中找出特定的key列表 & Scan详解
Redis进阶-如何从海量的 key 中找出特定的key列表 & Scan详解
597 0
|
NoSQL Redis 数据安全/隐私保护
【Redis 技术探索】「数据迁移实战」手把手教你如何实现在线 + 离线模式进行迁移 Redis 数据实战指南(scan模式迁移)
【Redis 技术探索】「数据迁移实战」手把手教你如何实现在线 + 离线模式进行迁移 Redis 数据实战指南(scan模式迁移)
332 0
【Redis 技术探索】「数据迁移实战」手把手教你如何实现在线 + 离线模式进行迁移 Redis 数据实战指南(scan模式迁移)
|
存储 NoSQL 算法
【Redis核心原理专题】(1)「技术提升系列」分析探究如何实现LFU的热点key发现机制以及内部的Scan扫描技术的原理
【Redis核心原理专题】(1)「技术提升系列」分析探究如何实现LFU的热点key发现机制以及内部的Scan扫描技术的原理
189 0
【Redis核心原理专题】(1)「技术提升系列」分析探究如何实现LFU的热点key发现机制以及内部的Scan扫描技术的原理
|
NoSQL Java 程序员
Redis scan 命令的一次坑
Redis scan 命令的一次坑
323 0
|
NoSQL Redis
Redis的scan命令学习
Redis的scan命令学习
219 0
|
NoSQL Shell Redis
Redis 中 scan 命令太坑了,千万别乱用!!
原本以为自己对redis命令还蛮熟悉的,各种数据模型各种基于redis的骚操作。但是最近在使用redis的scan的命令式却踩了一个坑,顿时发觉自己原来对redis的游标理解的很有限。所以记录下这个踩坑的过程,背景如下:
3459 1
Redis 中 scan 命令太坑了,千万别乱用!!
|
NoSQL Java Linux
使用redis的scan指令详解
使用redis的scan指令详解
544 0
|
存储 NoSQL 算法
Redis之Scan
Redis之Scan
1188 0
|
NoSQL Java 数据库
Redis命令:scan实现模糊查询
Redis命令:scan实现模糊查询
496 0