Luke a Pro

Luke Sun

Developer & Marketer

🇺🇦
EN||

一致性哈希 (Consistent Hashing)

| , 3 minutes reading.

一致性哈希:平衡之环

算法背后的故事:营火晚会的逻辑

想象一群童子军(10 个人)围成一个大圈坐在营火旁。每个人负责捕捉飞入他们那一段圆弧区域的“萤火虫”。

如果有第 11 個童子军加入,大家只需要稍微挪动一下位置。只有新成员左右两边的两名童子军需要交出他们的一小部分管辖区域。剩下的 8 名童子军手里的萤火虫完全不需要动位置。

现在,想象如果他们使用严格的编号系统:“如果萤火虫的 ID 是 XX,那么编号为 (Xmod10)(X \mod 10) 的人负责捕捉。” 如果第 11 个人加入,公式变成了 (Xmod11)(X \mod 11)。突然之间,营地里几乎每一只萤火虫都必须移交给不同的人。这就是再哈希灾难 (Rehash Disaster)

1997 年,麻省理工学院 (MIT) 的 David Karger 及其团队提出了 一致性哈希 (Consistent Hashing)。它确保了当集群规模变化时,只有 1/N1/N 的数据需要搬家。

为什么需要它?

一致性哈希是分布式系统可扩展性 (Scalability) 的基石。

  • 分布式缓存 (Memcached/Redis): 增加一台缓存服务器不应该导致全量缓存失效。
  • 负载均衡: 将 Web 请求均匀分布在一組服务器上。
  • 分布式数据库 (Cassandra/DynamoDB): 将数十亿行数据分散到数百个节点上。

算法是如何“思考”的

这个算法像是一个环形架构师

  1. 哈希环: 它将所有可能的哈希值想象成一个闭合的圆环。
  2. 节点入驻: 它将服务器(节点)的 ID 进行哈希,并放置在环上的随机点。
  3. 数据落位: 它将数据(如用户 ID)进行哈希,也放置在环上。
  4. 顺时针规则: 沿着环顺时针方向走,数据遇到的第一个节点就是它的负责人。
  5. 虚拟节点: 为了防止某个节点“运气不好”分配到过多数据,它为每个服务器创建了许多“虚拟节点”——即散布在环上各处的替身,以确保数据分配绝对均匀。

工程决策:最小扰动

  • 节点增加: 当新节点加入时,它只从其逆时针方向的紧邻邻居那里“分担”一部分数据。
  • 节点移除: 当某个节点故障时,它的数据只需“顺延”给环上的下一个邻居。
  • 稳定性: 在一个拥有 1000 个节点的系统中,增加 1 个节点仅会扰动 0.1% 的数据。而如果没有一致性哈希,这个数字将接近 99.9%

实现参考 (Python)

import hashlib

class ConsistentHashRing:
    def __init__(self, nodes=None, replicas=3):
        self.replicas = replicas # 虚拟节点数量
        self.ring = {}
        self.sorted_keys = []
        if nodes:
            for node in nodes:
                self.add_node(node)

    def _hash(self, key):
        return int(hashlib.md5(key.encode()).hexdigest(), 16)

    def add_node(self, node):
        for i in range(self.replicas):
            h = self._hash(f"{node}:{i}")
            self.ring[h] = node
            self.sorted_keys.append(h)
        self.sorted_keys.sort()

    def get_node(self, data_key):
        if not self.ring: return None
        h = self._hash(data_key)
        
        # 顺时针寻找第一个节点
        for node_hash in self.sorted_keys:
            if h <= node_hash:
                return self.ring[node_hash]
        
        # 如果走到了环的尽头,绕回第一个节点
        return self.ring[self.sorted_keys[0]]

# 使用示例
ring = ConsistentHashRing(["Server_A", "Server_B", "Server_C"])
print(f"用户 'luke' 分配到的服务器: {ring.get_node('luke')}")

小结

一致性哈希教会我们:真正的规模化需要局部化变化。通过从僵化的全局公式转向灵活的环形排列,我们可以让系统在成长过程中避免混沌。它提醒我们,在一个不断变化的世界中,最好的系统是那些能够适应变化而不打破全局宁静的系统。