0%

redis中scan的详解

[TOC]

实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 # 从开始开始遍历
scan 0 MATCH key* COUNT 10
# 或者
scan 0 MATCH "key*" COUNT 10

# 它有两个返回值,第一个返回值,表示下一次游标开始的位置,第二个返回值,是一个key的数组,当返回的游标再一次是"0"时,表示遍历完成。(这是唯一迭代结束的判定方式,而不能通过返回结果集是否为空判断迭代结束。)
1) "76"
2) 1) "key56"
2) "key91"
3) "key17"
4) "key51"
5) "key54"
6) "key86"
7) "key98"
8) "key0"
9) "key41"
10) "key97"
11) "key82"

# 例如,下一次迭代
scan 76 MATCH "key*" COUNT 10

和keys对比

keys *的缺点

  • 没有 offset、limit 参数,一次性吐出所有满足条件的 key,万一实例中有几百 w 个 key 满足条件,
  • keys 算法是遍历算法,复杂度是 O(n),如果实例中有千万级以上的 key,这个指令就会导致 Redis 服务卡顿,
  • 所有读写 Redis 的其它的指令都会被延后甚至会超时报错,
  • 因为 Redis 是单线程程序,顺序执行所有指令,其它指令必须等到当前的 keys 指令执行完了才可以继续。
  • 建议生产环境屏蔽keys命令

大key扫描

有时候会因为业务人员使用不当,在 Redis 实例中会形成很大的对象,比如一个很大的 hash,一个很大的 zset 这都是经常出现的。这样的问题是在内存分配上,如果一个 key 太大,那么当它需要扩容时,会一次性申请更大的一块内存,这也会导致卡顿。如果这个大 key 被删除,内存会一次性回收,卡顿现象会再一次产生。

如何定位大key

scan 指令,对于扫描出来的每一个 key,使用 type 指令获得 key 的类型,然后使用相应数据结构的 size 或者 len 方法来得到它的大小,对于每一种类型,保留大小的前 N 名作为扫描结果展示出来。

上面这样的过程需要编写脚本,比较繁琐,不过 Redis 官方已经在 redis-cli 指令中提供了这样的扫描功能,我们可以直接拿来即用。

1
2
3
4
redis-cli  --bigkeys
# 如果你担心这个指令会大幅抬升 Redis 的 ops 导致线上报警,还可以增加一个休眠参数。
redis-cli --bigkeys -i 0.1
# 上面这个指令每隔 100 条 scan 指令就会休眠 0.1s,ops 就不会剧烈抬升,但是扫描的时间会变长。

需要注意的点

count选项

虽然增量式迭代命令不保证每次迭代所返回的元素数量, 但我们可以使用 COUNT 选项, 对命令的行为进行一定程度上的调整。

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

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

COUNT 参数的默认值为 10

MATCH 选项

增量式迭代命令也可以通过提供一个 glob 风格的模式参数, 让命令只返回和给定模式相匹配的元素。需要注意的是, 对元素的模式匹配工作是在命令从数据集中取出元素之后, 向客户端返回元素之前的这段时间内进行的, 所以如果被迭代的数据集中只有少量元素和模式相匹配, 那么迭代命令或许会在多次执行中都不返回任何元素。

redis server支持多个迭代并发进行

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

缺点

同一个元素可能会被返回多次。 处理重复元素的工作交由应用程序负责, 比如说, 可以考虑将迭代返回的元素仅仅用于可以安全地重复执行多次的操作上。

如果一个元素是在迭代过程中被添加到数据集的, 又或者是在迭代过程中从数据集中被删除的, 那么这个元素可能会被返回, 也可能不会, 这是未定义的(undefined)。

遍历的过程中如果有数据修改,改动后的数据能不能遍历到是不确定的。

参考

http://doc.redisfans.com/key/scan.html

http://jinguoxing.github.io/redis/2018/09/04/redis-scan/