【问题标题】:Do any hashtables (in-memory, non-distributed) use consistent hashing?任何哈希表(内存中,非分布式)是否使用一致的哈希?
【发布时间】:2012-12-19 07:32:08
【问题描述】:

我不是在谈论分布式键/值系统,例如通常与 memcached 一起使用的分布式键/值系统,它使用一致的哈希来使添加/删除节点成为一个相对便宜的过程。

我说的是你的标准内存哈希表,比如 python 的 dict 或 perl 的哈希。

通过降低调整哈希表大小的成本,使用一致哈希的好处似乎也适用于这些标准数据结构。实时系统(和其他对延迟敏感的系统)将受益于/需要针对低成本增长进行优化的哈希表,即使整体吞吐量略有下降。

维基百科提到“增量调整大小”,但基本上是在谈论调整大小的热/冷替换方法;有一篇关于“可扩展散列”的单独文章使用 trie 进行存储桶查找来完成廉价的重新散列。

只是好奇是否有人听说过使用一致哈希来降低增长成本的核心单节点哈希表。还是使用其他方法(如上面列出的两个维基百科位)更好地满足此要求?

或者......我的整个问题是否被误导了?内存分页考虑是否使复杂性不值得?也就是说,一致性哈希的额外间接性让您只重新哈希总键的一小部分,但这可能并不重要,因为您可能必须从每个现有页面中读取,因此内存延迟是您的主要因素,以及是否与内存访问的成本相比,您重新散列部分或全部键并不重要....但另一方面,通过一致的散列,您的所有键重映射都有相同的目标页面,所以会有与您的键重新映射到任何现有页面相比,内存抖动更少。

编辑:添加“数据结构”标签,澄清最后一句说“页面”而不是“桶”。

【问题讨论】:

  • 快速浏览一下维基百科的描述,我当然看不出重点。您似乎保存了重新散列和一些表洗牌,但散列函数无论如何都必须快速,移动条目很便宜(与分布式上下文不同),并且很少发生调整大小(有一个体面的增长策略),额外的间接性会减慢 all 查找。但也许我错过了什么。
  • delnan - 是的,你只节省了重新散列,代价是每次查找时都要访问另一个内存。但是,如果您对延迟敏感,则不一定能承受大量计划外的重新散列。类似于为什么人们不使用垃圾收集语言编写实时系统..

标签: data-structures hashtable consistent-hashing


【解决方案1】:

我还没有听说过这个,但是这可能是个好主意如果你选择了正确的一致性哈希实现。具体来说,谷歌等人的Jump Consistent Hashing。首先我会介绍为什么要 Jump,然后我会介绍它如何在本地数据结构中发挥作用。

跳转一致性哈希

Jump Consistent Hashing(我将简称为 Jump)非常适合这个领域,原因有几个。 Jump 假设节点不会失败,这对于本地数据结构来说非常有用,因为它们不会失败!这使得 Jump 仅是到一系列数字[0, numBuckets) 的映射,只需要 2-4 个字节的空间。

进一步实现简单快速。如果我们删除参考实现的浮点除法并用一半的整数除法替换它们,速度会更快。 (顺便说一句,我们可以。)

所有这些都可以用于...的变体

并发哈希映射

但首先,Java 的Concurrent Hash Map 处于高水平。

Java 的 ConcurrentHashMap 由多个 buckets 参数化。这个分片因子在地图的整个生命周期中都是不变的。这些桶中的每一个本身就是一个带有自己锁的哈希映射。

将键值对插入映射时,键被散列到其中一个桶中。获取该键的锁,并在释放锁之前将该项目插入到存储桶的哈希映射中。在插入桶x时,另一个线程可以同时插入桶y,但如果插入桶x,它将等待锁定。因此Java的ConcurrentHashMap有n路并发,其中n是构造函数的bucket参数。

就像任何哈希映射一样,ConcurrentHashMap 中的存储桶可能会被填满并需要增长。就像常规的哈希映射一样,它通过将其大小加倍并将桶中的所有内容重新哈希回更大的自身来实现这一点。除了“它更大的自我”只是桶的“自我”。如果一个桶是热点并且获得的密钥超过其公平份额,那么与其他桶相比,该桶将不成比例地增长。并且每次存储桶增长时,重新散列到自身所需的时间就会越来越长。最后一点不仅是热点问题,而且当哈希表普通旧获取更多键时。

想象一下,如果我们可以随着键数量的增加而增加桶的数量。有了这个,我们可以抑制每个桶的增长量。

输入一致性哈希,这样我们就可以添加更多的桶了!

ConcurrentHashMap take 2:一致的哈希样式

我们可以通过两个简单的步骤让 ConcurrentHashMap 增加它的桶数。

首先将映射到每个桶的函数替换为跳转一致哈希函数。到目前为止,一切都应该是一样的。

当一个bucket被填满时,第二次分裂出一个新的bucket;也长满了桶。实际上,只有当装满的桶成为容量最大的桶时,才拆分出一个新的桶。这可以在不迭代桶的情况下计算出来。

通过一致的散列,拆分只会将键定向到新存储桶中,而不会倒退到任何旧存储桶中。

尾注

我确信这个方案可以改进。也就是说,拆分存储桶需要进行全表扫描才能将键移动到新存储桶中。这肯定不比普通哈希映射差,而且可能更好,但它对可能不需要进行完整扫描的 ConcurrentHashMap 实现不利。

【讨论】:

  • 感谢您的详细解答!我会去阅读 Jump Consistent Hashing。
猜你喜欢
  • 1970-01-01
  • 2014-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-27
  • 2023-03-11
  • 2011-05-19
  • 1970-01-01
相关资源
最近更新 更多