一致性Hash算法

http://blog.youkuaiyun.com/sparkliang/article/details/5279393

3 consistent hashing 算法的原理

consistent hashing 是一种 hash 算法,简单的说,在移除 / 添加一个 cache 时,它能够尽可能小的改变已存在 key 映射关系,尽可能的满足单调性的要求。

下面就来按照 5 个步骤简单讲讲 consistent hashing 算法的基本原理。

3.1 环形hash 空间

考虑通常的 hash 算法都是将 value 映射到一个 32 为的 key 值,也即是 0~2^32-1 次方的数值空间;我们可以将这个空间想象成一个首( 0 )尾( 2^32-1 )相接的圆环,如下面图 1 所示的那样。

circle space

图 1 环形 hash 空间

3.2 把对象映射到hash 空间

接下来考虑 4 个对象 object1~object4 ,通过 hash 函数计算出的 hash 值 key 在环上的分布如图 2 所示。

hash(object1) = key1;

… …

hash(object4) = key4;

object

图 2 4 个对象的 key 值分布

3.3 把cache 映射到hash 空间

Consistent hashing 的基本思想就是将对象和 cache 都映射到同一个 hash 数值空间中,并且使用相同的hash 算法。

假设当前有 A,B 和 C 共 3 台 cache ,那么其映射结果将如图 3 所示,他们在 hash 空间中,以对应的 hash值排列。

hash(cache A) = key A;

… …

hash(cache C) = key C;

cache

图 3 cache 和对象的 key 值分布

 

说到这里,顺便提一下 cache 的 hash 计算,一般的方法可以使用 cache 机器的 IP 地址或者机器名作为hash 输入。

3.4 把对象映射到cache

现在 cache 和对象都已经通过同一个 hash 算法映射到 hash 数值空间中了,接下来要考虑的就是如何将对象映射到 cache 上面了。

在这个环形空间中,如果沿着顺时针方向从对象的 key 值出发,直到遇见一个 cache ,那么就将该对象存储在这个 cache 上,因为对象和 cache 的 hash 值是固定的,因此这个 cache 必然是唯一和确定的。这样不就找到了对象和 cache 的映射方法了吗?!

依然继续上面的例子(参见图 3 ),那么根据上面的方法,对象 object1 将被存储到 cache A 上; object2和 object3 对应到 cache C ; object4 对应到 cache B ;

3.5 考察cache 的变动

前面讲过,通过 hash 然后求余的方法带来的最大问题就在于不能满足单调性,当 cache 有所变动时,cache 会失效,进而对后台服务器造成巨大的冲击,现在就来分析分析 consistent hashing 算法。

3.5.1 移除 cache

考虑假设 cache B 挂掉了,根据上面讲到的映射方法,这时受影响的将仅是那些沿 cache B 逆时针遍历直到下一个 cache ( cache C )之间的对象,也即是本来映射到 cache B 上的那些对象。

因此这里仅需要变动对象 object4 ,将其重新映射到 cache C 上即可;参见图 4 。

remove

图 4 Cache B 被移除后的 cache 映射

3.5.2 添加 cache

再考虑添加一台新的 cache D 的情况,假设在这个环形 hash 空间中, cache D 被映射在对象 object2 和object3 之间。这时受影响的将仅是那些沿 cache D 逆时针遍历直到下一个 cache ( cache B )之间的对象(它们是也本来映射到 cache C 上对象的一部分),将这些对象重新映射到 cache D 上即可。

 

因此这里仅需要变动对象 object2 ,将其重新映射到 cache D 上;参见图 5 。

add

图 5 添加 cache D 后的映射关系

4 虚拟节点

考量 Hash 算法的另一个指标是平衡性 (Balance) ,定义如下:

平衡性

  平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。

hash 算法并不是保证绝对的平衡,如果 cache 较少的话,对象并不能被均匀的映射到 cache 上,比如在上面的例子中,仅部署 cache A 和 cache C 的情况下,在 4 个对象中, cache A 仅存储了 object1 ,而 cache C 则存储了 object2 、 object3 和 object4 ;分布是很不均衡的。

为了解决这种情况, consistent hashing 引入了“虚拟节点”的概念,它可以如下定义:

“虚拟节点”( virtual node )是实际节点在 hash 空间的复制品( replica ),一实际个节点对应了若干个“虚拟节点”,这个对应个数也成为“复制个数”,“虚拟节点”在 hash 空间中以 hash 值排列。

仍以仅部署 cache A 和 cache C 的情况为例,在图 4 中我们已经看到, cache 分布并不均匀。现在我们引入虚拟节点,并设置“复制个数”为 2 ,这就意味着一共会存在 4 个“虚拟节点”, cache A1, cache A2 代表了 cache A ; cache C1, cache C2 代表了 cache C ;假设一种比较理想的情况,参见图 6 。

virtual nodes

图 6 引入“虚拟节点”后的映射关系

 

此时,对象到“虚拟节点”的映射关系为:

objec1->cache A2 ; objec2->cache A1 ; objec3->cache C1 ; objec4->cache C2 ;

因此对象 object1 和 object2 都被映射到了 cache A 上,而 object3 和 object4 映射到了 cache C 上;平衡性有了很大提高。

引入“虚拟节点”后,映射关系就从 { 对象 -> 节点 } 转换到了 { 对象 -> 虚拟节点 } 。查询物体所在 cache时的映射关系如图 7 所示。

map

图 7 查询对象所在 cache

 

“虚拟节点”的 hash 计算可以采用对应节点的 IP 地址加数字后缀的方式。例如假设 cache A 的 IP 地址为202.168.14.241 。

引入“虚拟节点”前,计算 cache A 的 hash 值:

Hash(“202.168.14.241”);

引入“虚拟节点”后,计算“虚拟节”点 cache A1 和 cache A2 的 hash 值:

Hash(“202.168.14.241#1”);  // cache A1

Hash(“202.168.14.241#2”);  // cache A2


### 一致性哈希算法的原理与实现 #### 1. 原理概述 一致性哈希算法的核心思想是将整个哈希值空间组织成一个虚拟的圆环,称为 **Hash 环**。这个环的范围通常为 0 到 \(2^{32}-1\),即一个 32 位无符号整型数[^3]。通过这种设计,可以解决传统哈希算法在分布式系统中动态伸缩时导致的大规模数据迁移问题。 具体步骤如下: - **步骤一**:定义一个 Hash 函数,将所有可能的键值(Key)映射到该环上的某个位置。 - **步骤二**:将服务器节点也通过相同的 Hash 函数映射到环上。 - **步骤三**:当需要定位某个键值对应的服务器时,计算该键值的哈希值,并从其位置沿环顺时针寻找最近的一个服务器节点。这个服务器节点即为负责存储该键值的服务器[^5]。 #### 2. 实现细节 以下是一致性哈希算法的实现关键点: - **Hash 函数的选择**:通常使用 MD5 或 SHA-1 等成熟的哈希函数来生成固定长度的哈希值。 - **虚拟节点的引入**:为了避免物理节点分布不均导致的负载倾斜问题,可以通过引入虚拟节点(Virtual Node)来平衡负载。每个物理节点可以对应多个虚拟节点,这些虚拟节点均匀分布在环上[^4]。 - **节点增减的影响**:当添加或移除节点时,只有部分数据需要重新分配,其余数据的映射关系保持不变。这是由于一致性哈希算法的设计使得数据的迁移仅限于受影响的相邻节点之间[^1]。 #### 3. 示例代码实现 以下是一个简单的 Python 实现示例,展示了如何构建一致性哈希环并定位键值到服务器: ```python import hashlib class ConsistentHashing: def __init__(self, nodes=None, replicas=3): self.ring = {} self.nodes = [] self.replicas = replicas # 每个节点的虚拟节点数 if nodes: for node in nodes: self.add_node(node) def _hash(self, key): return int(hashlib.md5(str(key).encode()).hexdigest(), 16) def add_node(self, node): for i in range(self.replicas): virtual_node = f"{node}-{i}" hash_value = self._hash(virtual_node) self.ring[hash_value] = node self.nodes.append(node) def remove_node(self, node): for i in range(self.replicas): virtual_node = f"{node}-{i}" hash_value = self._hash(virtual_node) del self.ring[hash_value] self.nodes.remove(node) def get_node(self, key): if not self.ring: return None hash_value = self._hash(key) sorted_keys = sorted(self.ring.keys()) for k in sorted_keys: if k >= hash_value: return self.ring[k] return self.ring[sorted_keys[0]] # 如果超出最大值,则返回第一个节点 # 测试代码 nodes = ["192.168.1.1", "192.168.1.2", "192.168.1.3"] ch = ConsistentHashing(nodes) print(ch.get_node("data1")) # 输出负责存储 data1 的服务器 ``` #### 4. 优缺点分析 - **优点**: - 动态扩展性强,能够很好地适应节点的增减。 - 数据迁移量小,避免了传统哈希算法中大规模数据重分布的问题[^4]。 - **缺点**: - 虚拟节点的引入增加了复杂度。 - 在极端情况下,仍可能出现负载不均衡的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值