背景

一致性哈希(Consistent Hashing)的出现,源于大规模分布式系统中面临的一个核心问题:

当我们将数据或缓存分布在多个节点(机器)上时,如何让 key 分布合理、节点可扩缩容,并保持缓存命中率稳定?

传统哈希的缺陷(背景问题)

nodeIndex = hash(key) % N
  • N 是节点数量

  • key 是数据标识,比如 user_123
    问题来了:

如果你增加或减少节点(比如从 3 台变成 4 台),由于 N 变化,所有 key 的哈希结果都会变,导致所有缓存数据失效,重新缓存。

这种现象叫:

❌ “缓存雪崩”问题
一加节点,90% 的 key 映射变了 → 90% 缓存 miss → 都去查数据库 → 系统崩溃

系统无法平滑扩容 / 宕机恢复

一致性哈希的提出背景

一致性哈希最早是在 1997 年 MIT 论文《Consistent Hashing and Random Trees》 中被提出。

后来被 Amazon Dynamo、Cassandra、Riak、Memcached 分布式客户端(如 Ketama) 等广泛采用。

核心目的:
节点变化时,只重分布极少量的数据,提升系统稳定性和扩展性。

Code

package main

import (
	"crypto/md5"
	"fmt"
	"sort"
	"strconv"
)

// 哈希函数:返回 uint32 的哈希值
func hashKey(key string) uint32 {
	hash := md5.Sum([]byte(key))
	return (uint32(hash[0]) << 24) | (uint32(hash[1]) << 16) | (uint32(hash[2]) << 8) | uint32(hash[3])
}

// 一致性哈希结构
type ConsistentHash struct {
	replicas    int                 // 虚拟节点数量
	hashCircle  []uint32            // 排序后的虚拟节点 hash 环
	hashMap     map[uint32]string   // hash -> 节点名
	nodeStorage map[string][]string // 节点 -> 存储的 keys
}

func NewConsistentHash(replicas int) *ConsistentHash {
	return &ConsistentHash{
		replicas:    replicas,
		hashMap:     make(map[uint32]string),
		nodeStorage: make(map[string][]string),
	}
}

// 添加真实节点,同时添加虚拟节点
func (c *ConsistentHash) AddNode(node string) {
	for i := 0; i < c.replicas; i++ {
		virtualKey := node + "#" + strconv.Itoa(i)
		hash := hashKey(virtualKey)
		c.hashCircle = append(c.hashCircle, hash)
		c.hashMap[hash] = node
	}
	sort.Slice(c.hashCircle, func(i, j int) bool {
		return c.hashCircle[i] < c.hashCircle[j]
	})
}

// 获取 key 所属节点
func (c *ConsistentHash) GetNode(key string) string {
	if len(c.hashCircle) == 0 {
		return ""
	}
	hash := hashKey(key)
	// 二分查找
	idx := sort.Search(len(c.hashCircle), func(i int) bool {
		return c.hashCircle[i] >= hash
	})
	if idx == len(c.hashCircle) {
		idx = 0
	}
	return c.hashMap[c.hashCircle[idx]]
}

// 模拟存储 key 到节点
func (c *ConsistentHash) StoreKey(key string) {
	node := c.GetNode(key)
	c.nodeStorage[node] = append(c.nodeStorage[node], key)
	fmt.Printf("key '%s' 存储到节点 '%s'\n", key, node)
}

// 打印每个节点存储的 key
func (c *ConsistentHash) PrintStorage() {
	for node, keys := range c.nodeStorage {
		fmt.Printf("节点 %s 存储了 %d 个 key: %v\n", node, len(keys), keys)
	}
}

func main() {
	ch := NewConsistentHash(3) // 每个节点 3 个虚拟节点

	// 添加缓存节点
	ch.AddNode("CacheA")
	ch.AddNode("CacheB")
	ch.AddNode("CacheC")

	// 模拟存储多个 key
	keys := []string{"user_1", "user_2", "user_3", "user_4", "user_5", "user_123"}
	for _, k := range keys {
		ch.StoreKey(k)
	}

	// 查看各节点存储情况
	fmt.Println("\n分布情况:")
	ch.PrintStorage()

	name := ch.GetNode("user_1")
	fmt.Printf("user_1 存储在节点 %s\n", name)

}

🧩 总结

一致性哈希的背景就是为了解决分布式系统中,节点动态变化时,数据/缓存迁移代价高的问题,目标是让系统能:

稳定运行

平滑扩容

高命中率

动态容错