Redis,SCAN游标"state management"是如何工作的?
Redis, how does SCAN cursor "state management" work?
Redis 有一个 SCAN 命令,可用于迭代匹配模式等的键
您首先将游标值设为 0;每次调用 returns 一个新的游标值,您将其传递给下一个 SCAN 调用。值为 0 表示迭代已完成。假设不需要服务器或客户端状态(游标值除外)
我想知道 Redis 是如何实现扫描算法的?
你可以在 redis dict.c 源文件中找到答案。那我就引用一部分吧
迭代工作方式如下:
- 最初您使用游标 (v) 值 0 调用该函数。2)
- 函数执行迭代的一步,return是
必须在下次调用中使用的新游标值。
- 当returned游标为0时,迭代完成
该函数保证字典中存在的所有元素在迭代开始和结束之间得到 returned。但是,某些元素可能会被 returned 多次。对于每个 returned 元素,调用回调参数 'fn',第一个参数是 'privdata',第二个参数是字典条目'de'。
工作原理
迭代算法由Pieter Noordhuis设计。主要思想是从高位开始递增游标。也就是说,不是正常递增游标,而是反转游标的位,然后游标递增,最后再次反转位。
需要此策略,因为散列 table 可能会在迭代调用之间调整大小。 dict.c 散列 table 的大小始终为 2 的幂,并且它们使用链接,因此给定 table 中元素的位置通过计算 Hash(key ) 和 SIZE-1(其中 SIZE-1 始终是掩码,相当于对密钥的 Hash 和 SIZE 进行除法的其余部分)。
例如,如果当前散列 table 大小为 16,则掩码为(二进制)1111。散列 table 中的键的位置将始终是哈希输出,等等。
如果 table 大小发生变化会怎样?
如果散列 table 增长,元素可以在旧桶的一个倍数中的任何位置:例如,假设我们已经用 4 位游标 1100 进行了迭代(掩码是 1111,因为散列 table 尺寸 = 16).
如果散列 table 将调整为 64 个元素,则新掩码将为 111111。通过将 ??1100 替换为 0 或 1 获得的新桶只能通过键定位我们在扫描较小的哈希 table.
中的桶 1100 时已经访问过
通过先迭代高位,由于反向计数器,如果 table 大小变大,游标不需要重新启动。它将继续使用末尾没有“1100”的游标进行迭代,并且也没有已经探索的最后 4 位的任何其他组合。
类似地,当 table 大小随时间缩小时,例如从 16 变为 8,如果低三位的组合(大小 8 的掩码是 111)已经被完全探索,它将不会被再次访问,因为我们确定我们尝试过,例如,0111 和 1111(所有更高位的变体)所以我们不需要再次测试它。
等等...你有 两个 tables 在重新散列过程中!
是的,这是真的,但我们总是先迭代较小的table,然后我们测试当前游标的所有扩展到较大的table。例如,如果当前游标是 101,并且我们还有一个更大的 table,大小为 16,我们还会在更大的 table 中测试 (0)101 和 (1)101。这将问题减少到只有一个 table,其中较大的一个(如果存在)只是较小的一个扩展。
限制
这个迭代器是完全无状态的,这是一个巨大的优势,包括不使用额外的内存。
这种设计带来的缺点是:
- 有可能我们 return 个元素不止一次。然而这通常在应用程序级别很容易处理。
- 迭代器每次调用必须 return 多个元素,因为它需要始终 return 给定桶中链接的所有键以及所有扩展,因此我们确定我们不会遗漏在重新散列过程中移动的键。
- 反向光标一开始有点难以理解,但这条评论应该有所帮助。
Redis 有一个 SCAN 命令,可用于迭代匹配模式等的键
您首先将游标值设为 0;每次调用 returns 一个新的游标值,您将其传递给下一个 SCAN 调用。值为 0 表示迭代已完成。假设不需要服务器或客户端状态(游标值除外)
我想知道 Redis 是如何实现扫描算法的?
你可以在 redis dict.c 源文件中找到答案。那我就引用一部分吧
迭代工作方式如下:
- 最初您使用游标 (v) 值 0 调用该函数。2)
- 函数执行迭代的一步,return是
必须在下次调用中使用的新游标值。 - 当returned游标为0时,迭代完成
该函数保证字典中存在的所有元素在迭代开始和结束之间得到 returned。但是,某些元素可能会被 returned 多次。对于每个 returned 元素,调用回调参数 'fn',第一个参数是 'privdata',第二个参数是字典条目'de'。
工作原理
迭代算法由Pieter Noordhuis设计。主要思想是从高位开始递增游标。也就是说,不是正常递增游标,而是反转游标的位,然后游标递增,最后再次反转位。
需要此策略,因为散列 table 可能会在迭代调用之间调整大小。 dict.c 散列 table 的大小始终为 2 的幂,并且它们使用链接,因此给定 table 中元素的位置通过计算 Hash(key ) 和 SIZE-1(其中 SIZE-1 始终是掩码,相当于对密钥的 Hash 和 SIZE 进行除法的其余部分)。
例如,如果当前散列 table 大小为 16,则掩码为(二进制)1111。散列 table 中的键的位置将始终是哈希输出,等等。
如果 table 大小发生变化会怎样?
如果散列 table 增长,元素可以在旧桶的一个倍数中的任何位置:例如,假设我们已经用 4 位游标 1100 进行了迭代(掩码是 1111,因为散列 table 尺寸 = 16).
如果散列 table 将调整为 64 个元素,则新掩码将为 111111。通过将 ??1100 替换为 0 或 1 获得的新桶只能通过键定位我们在扫描较小的哈希 table.
中的桶 1100 时已经访问过通过先迭代高位,由于反向计数器,如果 table 大小变大,游标不需要重新启动。它将继续使用末尾没有“1100”的游标进行迭代,并且也没有已经探索的最后 4 位的任何其他组合。
类似地,当 table 大小随时间缩小时,例如从 16 变为 8,如果低三位的组合(大小 8 的掩码是 111)已经被完全探索,它将不会被再次访问,因为我们确定我们尝试过,例如,0111 和 1111(所有更高位的变体)所以我们不需要再次测试它。
等等...你有 两个 tables 在重新散列过程中!
是的,这是真的,但我们总是先迭代较小的table,然后我们测试当前游标的所有扩展到较大的table。例如,如果当前游标是 101,并且我们还有一个更大的 table,大小为 16,我们还会在更大的 table 中测试 (0)101 和 (1)101。这将问题减少到只有一个 table,其中较大的一个(如果存在)只是较小的一个扩展。
限制
这个迭代器是完全无状态的,这是一个巨大的优势,包括不使用额外的内存。 这种设计带来的缺点是:
- 有可能我们 return 个元素不止一次。然而这通常在应用程序级别很容易处理。
- 迭代器每次调用必须 return 多个元素,因为它需要始终 return 给定桶中链接的所有键以及所有扩展,因此我们确定我们不会遗漏在重新散列过程中移动的键。
- 反向光标一开始有点难以理解,但这条评论应该有所帮助。