【问题标题】:Redis, how does SCAN cursor "state management" work?Redis,SCAN 游标“状态管理”是如何工作的?
【发布时间】:2015-03-22 00:51:30
【问题描述】:

Redis 有一个 SCAN 命令,可用于迭代匹配模式等的键。

Redis SCAN doc

您首先将光标值设为 0;每次调用都会返回一个新的光标值,您将其传递给下一次 SCAN 调用。值 0 表示迭代已完成。假设不需要服务器或客户端状态(光标值除外)

我想知道 Redis 是如何实现扫描算法的?

【问题讨论】:

    标签: redis


    【解决方案1】:

    您可以在 redis dict.c 源文件中找到答案。那我就引用一部分吧。

    迭代的工作方式如下:

    1. 最初,您使用光标 (v) 值 0.2) 调用函数
    2. 该函数执行一步迭代,并返回
      下次调用时必须使用的新光标值。
    3. 当返回游标为0时,迭代完成。

    该函数保证字典中存在的所有元素在迭代的开始和结束之间返回。但是,某些元素可能会多次返回。对于返回的每个元素,调用回调参数“fn”,将“privdata”作为第一个参数,将字典条目“de”作为第二个参数。

    工作原理

    迭代算法由Pieter Noordhuis设计。主要思想是从高位开始增加光标。即不是正常递增光标,而是将光标的位反转,然后光标递增,最后再次反转。

    需要此策略是因为哈希表可能会在迭代调用之间调整大小。 dict.c 哈希表的大小始终是 2 的幂,并且它们使用链接,因此给定表中元素的位置是通过计算 Hash(key) 和 SIZE-1 之间的按位与来给出的(其中 SIZE-1 是总是相当于取键的哈希和大小之间的其余部分的掩码)。

    例如,如果当前哈希表大小为 16,则掩码为(二进制)1111。哈希表中键的位置将始终是哈希输出的最后四位,依此类推。

    如果表格大小发生变化会怎样?

    如果哈希表增长,元素可以进入旧存储桶的倍数中的任何位置:例如,假设我们已经使用 4 位游标 1100 进行了迭代(掩码为 1111,因为哈希表大小 = 16)。

    如果哈希表将调整大小为 64 个元素,则新掩码将为 111111。通过将 ??1100 替换为 0 或 1 获得的新存储桶只能由我们在扫描在较小的哈希表中存储桶 1100。

    通过先迭代高位,由于反转计数器,如果表大小变大,光标不需要重新启动。它将继续使用末尾没有“1100”的游标进行迭代,并且也没有任何其他已探索的最后 4 位组合。

    类似地,当表大小随着时间的推移而缩小时,例如从 16 到 8,如果低三位的组合(大小 8 的掩码为 111)已经被完全探索,则不会再次访问它,因为我们确定我们尝试过,例如 0111 和 1111(高位的所有变体),所以我们不需要再次测试它。

    等等...在重新散列期间您有 两个 表!

    是的,确实如此,但我们总是先迭代较小的表,然后将当前游标的所有扩展测试到较大的表中。例如,如果当前游标是 101,并且我们还有一个大小为 16 的较大表,我们还在较大的表中测试 (0)101 和 (1)101。这将问题减少到只有一张表,其中较大的一张(如果存在)只是较小一张的扩展。

    限制

    这个迭代器是完全无状态的,这是一个巨大的优势,包括不使用额外的内存。 这种设计的缺点是:

    1. 我们有可能多次返回元素。但是,这通常在应用程序级别很容易处理。
    2. 迭代器每次调用都必须返回多个元素,因为它需要始终返回链接在给定存储桶中的所有键以及所有扩展,因此我们确保不会错过重新散列期间移动的键。
    3. 反向光标一开始有点难以理解,但这条注释应该会有所帮助。

    【讨论】:

    • 我花了相当多的时间来写这个评论,看到它在这里发布真是一种解脱:-) 谢谢。
    • 你好,Salvatore。非常感谢所有想了解“魔法”实际上是如何工作的人的评论:)
    • 我真的很喜欢 Redis。看到干净、可读的源代码让我更喜欢它:)
    猜你喜欢
    • 2020-08-29
    • 1970-01-01
    • 2017-06-28
    • 2011-05-31
    • 1970-01-01
    • 2021-10-16
    • 2016-07-13
    • 2017-10-11
    • 1970-01-01
    相关资源
    最近更新 更多