【发布时间】:2015-03-22 00:51:30
【问题描述】:
Redis 有一个 SCAN 命令,可用于迭代匹配模式等的键。
您首先将光标值设为 0;每次调用都会返回一个新的光标值,您将其传递给下一次 SCAN 调用。值 0 表示迭代已完成。假设不需要服务器或客户端状态(光标值除外)
我想知道 Redis 是如何实现扫描算法的?
【问题讨论】:
标签: redis
Redis 有一个 SCAN 命令,可用于迭代匹配模式等的键。
您首先将光标值设为 0;每次调用都会返回一个新的光标值,您将其传递给下一次 SCAN 调用。值 0 表示迭代已完成。假设不需要服务器或客户端状态(光标值除外)
我想知道 Redis 是如何实现扫描算法的?
【问题讨论】:
标签: redis
您可以在 redis dict.c 源文件中找到答案。那我就引用一部分吧。
迭代的工作方式如下:
该函数保证字典中存在的所有元素在迭代的开始和结束之间返回。但是,某些元素可能会多次返回。对于返回的每个元素,调用回调参数“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。这将问题减少到只有一张表,其中较大的一张(如果存在)只是较小一张的扩展。
这个迭代器是完全无状态的,这是一个巨大的优势,包括不使用额外的内存。 这种设计的缺点是:
【讨论】: