🎯 什麼情境該想到我
當你要把資料/請求分散到多台節點(快取、分片 DB),又希望「增減節點時不要大搬風」時。
⚙️ 怎麼用
- 問題:用「hash(key) % N」分配,一旦 N 改變(加/減機器),幾乎所有 key 都要重新對應 → 快取全失效、大量資料搬移。
- 一致性雜湊:把節點與 key 都雜湊到一個環(0 ~ 2³²)上;key 順時針找到第一個節點。
- 增減節點時,只影響環上相鄰的一小段 key,其餘不動 → 資料搬移量最小化。
- 虛擬節點(vnodes):每個實體節點在環上放多個虛擬點,讓分佈更均勻、避免熱點。
★ 先算一下「為什麼不能用 % N」
1000 萬個 key,10 台機器 → hash(key) % 10
加到 11 台 → hash(key) % 11
★ 有多少 key 的歸屬沒變? 約 1/11 ≈ 9%
→ ★ 91% 的 key 要搬家 / 快取全部失效
一致性雜湊:
★ 只有「新節點接手的那一段」會變 → 約 1/N = 9% 的 key 搬家
→ 從 91% 降到 9%,這就是它唯一但巨大的價值。
★ 虛擬節點:不是優化,是必需品
❌ 沒有虛擬節點:3 個節點隨機落在環上
┌──────────────A──B────────────────────C──┐
★ A→B 之間很短,B→C 之間超長
→ ★ B 只拿到 5% 的資料,C 拿到 60% ← 嚴重不均
✅ 每個實體節點放 100~200 個虛擬節點
A B C A C B A B C B A C ...(散佈整個環)
→ ★ 大數法則讓每個實體節點接近 1/N
虛擬節點還解決第二個問題:★ 節點掛掉時的負載轉移
沒有虛擬節點:A 掛了 → ★ A 的「全部」負載壓到「下一個」節點 B → B 也被壓垮 → 雪崩
有虛擬節點: A 掛了 → A 的 200 個虛擬節點各自由不同的實體節點接手 → ★ 負載平均分攤
★ 這一點比「分佈均勻」更重要——它是防止連鎖故障的關鍵。
虛擬節點數的取捨:太少 → 不均;太多 → 環上的節點多、查找與 metadata 大。實務常見 100–256。
★ 實作(環用有序結構 + 二分搜尋)
type Ring struct {
hashes []uint32 // ★ 排序後的虛擬節點 hash
nodes map[uint32]string // hash → 實體節點
vnodes int
}
func (r *Ring) Add(node string) {
for i := 0; i < r.vnodes; i++ {
h := crc32.ChecksumIEEE([]byte(node + "#" + strconv.Itoa(i)))
r.hashes = append(r.hashes, h)
r.nodes[h] = node
}
slices.Sort(r.hashes) // ★ 見 [[快速排序]]
}
func (r *Ring) Get(key string) string {
h := crc32.ChecksumIEEE([]byte(key))
// ★ 二分搜尋找第一個 >= h 的虛擬節點(見 [[二分搜尋]])
i, _ := slices.BinarySearch(r.hashes, h)
if i == len(r.hashes) { i = 0 } // ★ 繞回環的開頭
return r.nodes[r.hashes[i]]
}★ 查找是 (V = 虛擬節點總數),不是 ——但常數極小。
★ 兩個更好的替代方案(值得知道)
| 方案 | 特點 |
|---|---|
| ★ Rendezvous hashing(HRW) | 對每個節點算 hash(key, node),取分數最高的。✅ 不需要環、不需要虛擬節點、分佈天然均勻、實作極簡 ❌ 查找是 (節點少時完全不是問題) ★ 節點數 < 幾百時,它通常是更好的選擇 |
| ★ Jump consistent hash(Google) | 極快、零記憶體、分佈完美 ❌ 只支援「在尾端加減節點」,不能任意移除中間的節點 ★ 適合分片數固定遞增的場景 |
| 固定數量的邏輯分區 | ★ 大型系統的實際做法(見 工具-資料分區): 一開始切 1024 片,只改「片→節點」的對應表。最可控。 |
★ 實務上的選擇順序:
- 固定邏輯分區 + 對應表(可控、可觀測、可手動調整)← 大型系統
- Rendezvous hashing(簡單、均勻)← 中小型
- 一致性雜湊 + 虛擬節點(經典、生態成熟)
一致性雜湊的名氣大於它今天的適用範圍——知道它,但不要預設就用它。
真實案例
| 系統 | 用法 |
|---|---|
| memcached 客戶端 | ★ 經典用例(ketama) |
| Cassandra / DynamoDB | 一致性雜湊 + 虛擬節點 |
| Envoy / nginx 的 upstream hash | 讓同一個使用者打到同一台(會話黏著) |
| CDN 的快取節點選擇 | Rendezvous 或一致性雜湊 |
🧪 我實際套用的紀錄
- 2026-07-15:(待填)
⚠️ 注意 / 什麼時候不適用
- ★ 一致性雜湊解決的是「搬遷量」,不是「均勻度」。均勻度靠虛擬節點——沒有虛擬節點的一致性雜湊幾乎不能用。
- ★ 它處理不了「熱 key」。一個爆紅的 key 永遠落在同一個節點上,虛擬節點也救不了。
→ 熱 key 要靠複製到多個節點或本地快取(見 工具-資料分區 的名人問題)。 - ★ 加節點時要「預熱」。新節點接手的那段 key 一開始全是 cache miss → 打到後端造成尖峰。
漸進式接手或預載入。 - 節點暫時失聯時不要立刻重新分配。網路抖動觸發搬遷 → 更多負載 → 更多失聯 → 雪崩。
要有延遲與人工閘門(見 工具-故障偵測)。 - 雜湊函式要快且分佈好。用 xxHash / MurmurHash,不要用加密雜湊(SHA-256 太慢)。
- ★ 快取層用它就好,有狀態資料要更小心。資料的搬遷牽涉一致性與可用性,
比「快取 miss 一次」嚴重得多——那時該考慮固定分區 + 明確的再平衡流程。 - 不同語言的客戶端要用同一套雜湊實作,否則會路由到不同節點(memcached 多語言環境的經典坑)。