一致性哈希算法与Java实现
一致性哈希算法与Java实现 一致性哈希算法, consistent hashing 概述 引出 登场 改进-虚节点 另一种改进 参考链接: 概述 在维基百科中,是这么定义的 一致哈希是一种特殊的哈希算法。在使用一致哈希算法后,哈希表槽位数 (大小) 的改变平均只需要对 K/n个关键字重新映射,其中K是关键字的数量, n是槽位数量。然而在传统的哈希表中,添加或删除一个槽位的几乎需要对所有关键字进行重新映射。 引出 我们在上文中已经介绍了一致性Hash算法的基本优势,我们看到了该算法主要解决的问题是: 当slot数发生变化时,能够尽量少的移动数据。那么,我们思考一下,普通的Hash算法是如何实现?又存在什么问题呢? 那么我们引出一个问题: 假设有1000w个数据项,100个存储节点,请设计一种算法合理地将他们存储在这些节点上。 看一看普通Hash算法的原理: normal_hash 算法的核心计算如下 1 2 3 4 5 6 for item in range(ITEMS): k = md5(str(item)).digest() h = unpack_from(">I", k)[0] # 通过取余的方式进行映射 n = h % NODES node_stat[n] += 1 具体的完整实现请参考normal_hash.py,输出是这样的: Ave: 100000 Max: 100695 (0.69%) Min: 99073 (0.93%) 从上述结果可以发现,普通的Hash算法均匀地将这些数据项打散到了这些节点上,并且分布最少和最多的存储节点数据项数目小于1%。之所以分布均匀,主要是依赖Hash算法 (实现使用的MD5算法) 能够比较随机的分布。 然而,我们看看存在一个问题,由于该算法使用节点数取余的方法,强依赖node的数目,因此,当是node数发生变化的时候,item所对应的node发生剧烈变化,而发生变化的成本就是我们需要在node数发生变化的时候,数据需要迁移,这对存储产品来说显然是不能忍的,我们观察一下增加node后,数据项移动的情况: 1 2 3 4 5 6 7 8 9 for item in range(ITEMS): k = md5(str(item)).digest() h = unpack_from(">I", k)[0] # 原映射结果 n = h % NODES # 现映射结果 n_new = h % NEW_NODES if n_new != n: change += 1 详细实现代码在normal_hash_add.py输出是这样的: Change: 9900989 (99.01%) 翻译一下就是,如果有100个item,当增加一个node,之前99%的数据都需要重新移动。 这显然是不能忍的,普通哈希算法的问题我们已经发现了,如何对其进行改进呢?没错,我们的一致性哈希算法闪亮登场。 登场 我们上节介绍了普通Hash算法的劣势,即当node数发生变化 (增加、移除) 后,数据项会被重新"打散",导致大部分数据项不能落到原来的节点上,从而导致大量数据需要迁移。 那么,一个亟待解决的问题就变成了: 当node数发生变化时,如何保证尽量少引起迁移呢?即当增加或者删除节点时,对于大多数item,保证原来分配到的某个node,现在仍然应该分配到那个node,将数据迁移量的降到最低。 一致性Hash算法的原理是这样的: consist_hash 1 2 3 4 5 6 7 8 9 10 for n in range(NODES): h = _hash(n) ring.append(h) ring.sort() hash2node[h] = n for item in range(ITEMS): ...