一致Hash

一致Hash是为了解决在实际的分布式项目中服务器动态删减而导致命中率非常低的情况而引入到分布式系统中的。
一致性哈希将整个哈希值空间组织成一个虚拟的圆环,按照逆时针对数据进行排序,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形),整个哈希空间环如下:

假设现在有4台服务器,我们按照对IP进行Hash计算出的Hash值在环上的位置如下图:

现在有4个数据,O1,O2,O3,O4通过Hash(key)后在环上的位置如下图:

IP服务器的HASH算法要和数据的HASH算法一致

下面按照顺时针寻找最近服务器的原则,将数据存储在就近的服务器上面去:

 

当出现服务宕机的情况

如上图,当出现(B服务器)宕机的情况时,新的数据将会自动将数据存到最近的服务器(C服务器),这样不会对A,D以及B的原始数据有任何影响。假设数据平均分布,则命中率依然有75%。

当出现新增服务器的情况

如下图,新增了(X服务器),这时候(O2)将会存到新的(X服务器),对于整个系统来说,只会对B服务器的原始数据有影响,而不会对A、C、D有任何的影响。

服务器越多,在出现宕机或者新增服务器的时候命中率越高。也就是说一致Hash在在服务器越多的情况下将能发挥更大的作用和优势。

Hash环的偏斜

在实际项目中,服务器的在Hash环上的分布不一定是我们理想中的状态,有可能大部分的数据都被分配到某一台服务器了,因此就引入了(虚拟节点)的概念。

虚拟节点:将一个服务器虚拟为两或者多个个服务器,增大在虚拟环上的分布,从而降低将数据存入一台服务器的概率。如下图:

虚拟节点的算法,可以使用下列方式:
Hash("192.168.1.100#1"); // NODE1-1
Hash("192.168.1.100#2"); // NODE1-2

遗留问题

假设有A、B、C三个服务器,还有一条数据O(ID,name,phone):
1、开始在B中存的是O(1,www,13434343344),
2、B宕机后,用户在C中查询,没有,因此在C中建一条缓存数据O(1,www,13434343344),
3、这个时候,B好了,再次访问B,就会再次新建一条缓存数据O(1,www,13434343344),某个时间用户修改了电话,那么这时在B中的数据为O(1,www,13434343333),
4、假设用户修改数据后的某个时间B再次宕机了,用户只能访问C获取数据,发现C中有数据,但是在C中的数据是一条未修改的数据(用户不知,所以是一条错误数据)。

我们应该怎么防止这种情况的发生?

<think>我们来实现一致哈希(ConsistentHashing)算法。一致哈希常用于分布式系统中,特别是在缓存和负载均衡场景中,它能够最小化节点增减时带来的数据迁移量。基本思想:1.将整个哈希值空间组织成个虚拟的圆环(通常用0到2^32-1表示)。2.将节点(如服务器)通过哈希函数映射到这个环上(通常用节点的IP或唯标识进行哈希)。3.将数据对象通过同样的哈希函数映射到环上。4.从数据映射的位置开始顺时针查找,遇到的第个节点就是该数据应该存储的节点。为了平衡性,我们引入虚拟节点(virtualnode)的概念。每个物理节点对应多个虚拟节点,虚拟节点会映射到环上的不同位置。这样可以让节点在环上分布更均匀,从而使得数据分布更均衡。实现步骤:1.定义环,使用个有序的数据结构(如SortedList)来存储虚拟节点在环上的位置。2.添加节点:为每个节点生成定数量的虚拟节点(每个虚拟节点有个唯标识,比如"node1#0","node1#1"等),然后计算每个虚拟节点的哈希值,并添加到环中。同时,我们需要维护虚拟节点到物理节点的映射。3.移除节点:移除该节点对应的所有虚拟节点,并更新映射关系。4.根据数据键值查找节点:计算数据键值的哈希值,然后在环上找到第个大于等于该哈希值的虚拟节点(顺时针查找)。如果找不到(即哈希值大于环中最大哈希值),则选择环上第个节点(环是环状的)。然后通过虚拟节点映射到物理节点。注意:哈希函数的选择,这里我们使用md5,然后取模2^32,也可以使用其他哈希函数如crc32。我们将实现个类`ConsistentHashing`,包含以下方法:-`__init__(self,nodes=None,replicas=100)`:初始化,可以传入初始节点列表,以及每个节点的虚拟节点数量(replicas)。-`add_node(self,node)`:添加个物理节点。-`remove_node(self,node)`:移除个物理节点。-`get_node(self,key)`:根据key返回对应的物理节点。代码实现如下:</think>以下是个使用Python实现的一致性哈希算法(Consistent Hashing),包含虚拟节点支持以实现更好的负载均衡: ```python import hashlib from bisect import bisect class ConsistentHash: def __init__(self, nodes=None, replicas=100): """ 初始化一致性哈希环 :param nodes: 初始节点列表 :param replicas: 每个节点的虚拟节点数量 """ self.replicas = replicas self.ring = {} # 哈希环:position -> node self.sorted_keys = [] # 排序的哈希键列表 if nodes: for node in nodes: self.add_node(node) def add_node(self, node): """添加节点到哈希环""" for i in range(self.replicas): # 生成虚拟节点标识 virtual_node = f"{node}#{i}" # 计算哈希值并映射到环上 key = self._hash(virtual_node) self.ring[key] = node self.sorted_keys.append(key) # 保持哈希键有序 self.sorted_keys.sort() def remove_node(self, node): """从哈希环移除节点""" for i in range(self.replicas): virtual_node = f"{node}#{i}" key = self._hash(virtual_node) del self.ring[key] self.sorted_keys.remove(key) def get_node(self, key): """根据键获取目标节点""" if not self.ring: return None # 计算键的哈希值 hash_key = self._hash(key) # 在环上找到第个大于等于该哈希值的位置 idx = bisect(self.sorted_keys, hash_key) % len(self.sorted_keys) return self.ring[self.sorted_keys[idx]] def _hash(self, key): """计算32位哈希值(返回0~2^32-1之间的整数)""" return int(hashlib.md5(key.encode()).hexdigest(), 16) % (2**32) # 使用示例 if __name__ == "__main__": # 创建带有3个节点的环 nodes = ["Node-A", "Node-B", "Node-C"] ch = ConsistentHash(nodes, replicas=100) # 测试键值路由 test_keys = ["user_123", "file_xyz", "session_abc", "data_999"] for key in test_keys: node = ch.get_node(key) print(f"Key '{key}' is assigned to → {node}") # 添加新节点 print("\nAdding Node-D...") ch.add_node("Node-D") for key in test_keys: node = ch.get_node(key) print(f"Key '{key}' is now assigned to → {node}") # 移除节点 print("\nRemoving Node-B...") ch.remove_node("Node-B") for key in test_keys: node = ch.get_node(key) print(f"Key '{key}' is now assigned to → {node}") ``` ### 代码解释: 1. **核心组件**: - `ring`:字典,存储哈希位置到节点的映射 - `sorted_keys`:排序的哈希键列表,用于快速查找 - `replicas`:每个物理节点的虚拟节点数量 2. **关键方法**: - `add_node()`:为每个物理节点创建多个虚拟节点(格式:`节点名#编号`),计算哈希值并加入环中 - `remove_node()`:删除节点及其所有虚拟节点 - `get_node()`:计算键的哈希值,使用二分查找定位最近的节点 - `_hash()`:使用MD5计算32位哈希值(实际应用可替换为更高效的哈希函数) 3. **虚拟节点作用**: - 通过增加虚拟节点(示例中每个物理节点100个虚拟节点) - 使节点在环上分布更均匀 - 减少节点增减时的数据迁移量 4. **路由过程**: - 计算键的哈希值 - 在排序的哈希环中找到第个 ≥ 该值的位置 - 通过取模运算实现环状结构 5. **优点**: - 节点变动时仅影响相邻节点 - 虚拟节点解决负载不均问题 - 时间复杂度:O(log N) 的查找效率(N为虚拟节点总数) ### 示例输出: ``` Key 'user_123' is assigned to → Node-C Key 'file_xyz' is assigned to → Node-A Key 'session_abc' is assigned to → Node-B Key 'data_999' is assigned to → Node-B Adding Node-D... Key 'user_123' is now assigned to → Node-C Key 'file_xyz' is now assigned to → Node-A Key 'session_abc' is now assigned to → Node-D # 部分键重新分配 Key 'data_999' is now assigned to → Node-B Removing Node-B... Key 'user_123' is now assigned to → Node-C Key 'file_xyz' is now assigned to → Node-A Key 'session_abc' is now assigned to → Node-D Key 'data_999' is now assigned to → Node-D # 原Node-B的键迁移到Node-D ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值