Redis扫描计数:如何强制SCAN返回所有与模式匹配的键?

44

我正在尝试从Redis中查找与模式匹配的键列表中存储的值。我尝试使用SCAN来获取所有值,以便稍后可以使用MGET。问题是:

SCAN 0 MATCH "foo:bar:*" COUNT 1000

不返回任何值,而

SCAN 0 MATCH "foo:bar:*" COUNT 10000

返回所需的键。我如何强制SCAN浏览所有现有的键?我需要查看lua吗?


9
强制 SCAN 一次性遍历整个 key 空间的效果等同于运行 KEYS 命令。请注意,SCAN 命令正是为此而引入的 - 而不是运行 KEYS 命令。 - Itamar Haber
1
@ItamarHaber SCAN 命令是否也会像 KEYS 命令一样在整个迭代过程中阻塞键空间(仅限一次迭代)? - DarthSpeedious
1
是的 - 几乎所有的操作都是阻塞的。 - Itamar Haber
4
有没有关于count参数最大值的建议? - Adam Szecowka
由于这些命令允许增量迭代,每次调用仅返回少量元素,因此它们可以在生产中使用,而不会像KEYS或SMEMBERS命令一样阻塞服务器长时间(甚至几秒钟)调用大量键或元素的集合。 - ives
https://redis.io/commands/scan - ives
3个回答

44

使用下面的代码,您将扫描来自游标0的前1000个对象。

SCAN 0 MATCH "foo:bar:*" COUNT 1000 

结果是,您将获得一个新的光标以进行回叫

SCAN YOUR_NEW_CURSOR MATCH "foo:bar:*" COUNT 1000

扫描接下来的1000个对象。然后,当您将COUNT从1000增加到10000并检索数据时,您会扫描更多的键,这样就匹配更多的键。

要扫描整个列表,需要重复调用SCAN,直到游标在响应中返回零(即整个扫描)。

使用INFO命令获取您的键数量,例如:

db0:keys=YOUR_AMOUNT_OF_KEYS,expires=0,avg_ttl=0

然后调用

SCAN 0 MATCH "foo:bar:*" COUNT YOUR_AMOUNT_OF_KEYS

2
如何强制 SCAN 命令在一次操作中查找所有现有的键,以查看是否存在匹配项? - DarthSpeedious
如果Redis中有很多键,这将会非常慢,对吧?我想我需要重新考虑我的方法了。是一次性搜索所有键更好呢,还是使用循环迭代游标更好呢? - DarthSpeedious
8
是的,在生产中这不是正确的方法。更好的方法是使用迭代器遍历游标,而 SCAN 命令就是为此而设计的。一直迭代直到游标返回零以进行完整扫描。这样每次都会扫描计数值。 - khanou
1
请注意,在大型生产数据库上,使用SCAN命令可能仍然会出现性能问题,因为您的应用程序需要对Redis进行多次(甚至很多次)调用。例如,请查看此讨论:https://github.com/xetorthio/jedis/issues/1338 - AbstractVoid
同意。我们如何决定“COUNT”批量大小?应该是100还是500还是1000还是5000? - roottraveller

29

以下是使用Python的redis库实现此操作的方法,供有兴趣的人参考:

import redis
redis_server = redis.StrictRedis(host=settings.redis_ip, port=6379, db=0)
mid_results = []
cur, results = redis_server.scan(0,'foo:bar:*',1000)
mid_results += results

while cur != 0:
    cur, results = redis_server.scan(cur,'foo:bar:*',1000)
    mid_results += results

final_uniq_results = set(mid_results)

我花了几天时间才明白这个问题,但基本上每个scan都会返回一个元组。

例如:

(cursor, results_list)

(5433L, [... keys here ...])
(3244L, [... keys here, maybe ...])
(6543L, [... keys here, duplicates maybe too ...])
(0L, [... last items here ...])
  • 保持扫描 cursor 直到其返回 0
  • 有保证它将返回 0
  • 即使在扫描之间返回空的 results_list
  • 但是,正如@Josh在评论中指出的那样,在插入同时发生的竞态条件下,SCAN不能保证终止。

我很难弄清楚游标号是什么以及为什么会随机获取一个空列表或重复项,尽管我知道我刚刚放置了项目。

阅读后:

它更加合理,但仍然存在一些深层次的编程技巧和妥协来迭代集合。


1
非常感谢。不过我要补充一点, SCAN 并不能保证终止。在大多数情况下,它会终止,但如果 DB 中的项目数量继续增长并超过迭代器,它就不会停止。根据 Redis 官方文档:...仅在可迭代集合的大小保持有限到给定的最大大小时才保证终止 - Josh
1
@Josh 很棒的观点,谢天谢地我没有遇到这个问题,但这很有道理。 - jmunsch

5

如果您的使用场景涉及Python,或者您只想在机器上安装了Python并且只想一次性获取值,那么如果您使用redis python库中的scan_iter方法,这将是一个微不足道的任务:

from redis import StrictRedis

redis = StrictRedis.from_url(REDIS_URI)

keys = []
for key in redis.scan_iter('foo:bar:*', 1000):
    keys.append(key)

最终,keys将包含通过应用@khanou方法获得的所有键。
这也比执行shell脚本更有效率,因为那些在循环的每次迭代中都会生成一个新的客户端。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接