Sharding & Hashing

一致性雜湊:讓節點上下線只搬必要的資料

Consistent Hashing 把節點與資料鍵放到同一個 hash ring 上,讓擴容、縮容、節點故障時只重新分配相鄰區間,而不是讓整個叢集大洗牌。它是 cache cluster、distributed storage、load balancer 與 sharded database 中最常見的資料分派基礎。

Consistent Hashing Virtual Nodes Key Remapping
01

總覽與定位

為什麼普通 modulo sharding 在擴縮容時會失控,而 hash ring 可以把搬遷限制在局部。

最直覺的 sharding 做法是把資料鍵做 hash,再用節點數取餘數:hash(key) % N。問題在於 N 一變,幾乎每個 key 的目標節點都會改變。對 cache 來說這會造成大量 miss;對 storage 來說這代表昂貴的 data migration;對 online service 來說,擴容本來是為了降載,卻可能先製造一波重分片風暴。

核心一致性雜湊把「節點數」移出路由公式

資料不再依賴 % N,而是被映射到固定大小的環上。key 往順時針找到的第一個節點就是 owner;新增或移除節點時,只會影響該節點前一段區間。

策略 路由方式 節點數變動時 典型用途
Modulo Hashing hash(key) % N 大多數 key 重新指派 固定節點數、離線批次分桶
Consistent Hashing hash ring 順時針找 successor 只搬受影響的相鄰區間 Cache、storage node、動態 shard pool
Rendezvous Hashing key 對所有節點打分取最高 只影響加入或移除節點相關 key 節點數中小、需要簡潔實作的 client-side routing

一致性雜湊不是讓資料永不移動,而是讓移動範圍與變動節點相關,而非與整個叢集規模相關。

一句話答法Consistent Hashing 用固定 hash ring 取代 % N,因此節點上下線時只重分配局部區間,降低 cache miss 與資料搬遷成本。
02

Ring 演算法

把節點與 key 映射到同一個環,使用 successor rule 決定 owner。

一致性雜湊通常把 hash space 視為一個首尾相連的範圍,例如 0..359 或 0..2^32-1。每台節點先用 node id 做 hash 放上 ring;每個資料鍵也做 hash 放上 ring。查詢時從 key 的位置順時針走,遇到的第一個節點就是負責該 key 的 owner。

owner(key) = first node clockwise from hash(key)

當新增節點 D 時,它只會接手自己與前一個節點之間的 key;當節點 B 掛掉時,原本屬於 B 的 key 會順時針交給下一個節點。其他 ring 區間不需要搬動。

陷阱hash ring 不等於完全平均

若只有少量實體節點,每台機器在 ring 上的位置可能剛好很近或很遠,導致負載不均。Production 實作幾乎都會搭配 virtual nodes 或 weighted replicas。

function lookup(key, sortedRing) {
  const point = hash(key);
  const owner = sortedRing.find(node => node.point >= point);
  return owner || sortedRing[0]; // wrap around
}
一句話答法查 key 時不問「有幾台節點」,只問「key 在 ring 上順時針遇到誰」,因此節點集合變動只影響相鄰切片。
03

Virtual Nodes

用多個虛擬節點攤平負載,也讓不同容量的機器可以用權重參與分配。

Virtual Node(vnode)是把一台實體節點映射成多個 ring 上的位置,例如 A#1、A#2、A#3。key lookup 仍然找到第一個 vnode,但實際請求會送到該 vnode 背後的實體節點。vnode 越多,隨機 hash 造成的不均就越容易被平均掉。

設計點 沒有 vnode 有 vnode
負載平衡 少量節點時容易偏斜 每台節點散布多段區間,分配更平滑
異質容量 所有節點大致同權 大機器給更多 vnode,小機器給較少 vnode
搬遷粒度 一次搬一大段連續區間 拆成多個小區間,可逐步搬遷與節流
維運成本 metadata 少 ring metadata 較多,需要一致發布

vnode 數不是越多越好。太少會負載不均;太多會增加 routing table、membership gossip、rebalance plan 的成本。常見做法是按服務規模選一個足以平滑分布的 vnode 數,再用容量權重微調。

一句話答法Virtual nodes 用「多點代表一台機器」平滑 hash ring,也讓容量大的節點自然承接更多 key range。
04

擴縮容與故障

一致性雜湊降低 remapping,但 production 還要處理資料搬遷、複本與 membership 一致性。

在真實系統裡,ring 變更不是單純把節點加入陣列。資料或 cache entry 需要搬遷,client 需要拿到同一版 ring,replica placement 不能把多個複本放在同一故障域,否則一次 rack failure 仍然可能打掉多份資料。

  1. Membership change:控制面確認節點健康、容量與版本,再產生新的 ring version。
  2. Data movement:新 owner 從舊 owner backfill key range;過程中可能需要 dual read 或 forwarding。
  3. Traffic cutover:client 或 gateway 切到新 ring version,必要時保留短暫相容窗口。
  4. Replica awareness:第二、第三複本通常取後續不同 failure domain 的節點,而不是只看下一個 vnode。
實務一致性雜湊解的是 routing churn,不是完整 rebalance 系統

它讓「哪些 key 需要搬」變少,但仍需要流量節流、背景複製、健康檢查、ring version 管理與監控告警,才能安全地在 production 擴縮容。

一句話答法節點變動時只搬相鄰 range,但 production 必須把 ring 變更包進 membership、backfill、cutover 與 replica placement 流程。
05

互動 Demo

切換情境,觀察新增或移除節點時哪些 key 真的需要 remap。

下方模擬器用簡化的 0..359 ring 展示 4 個實體節點與 12 個 key。切換情境後,受影響的 key 會被標記出來;你可以直觀看到一致性雜湊的核心價值:不是零搬遷,而是只搬必要的一小段。

互動 · Consistent Hashing Ring 切換節點變動情境
Consistent hashing ring with keys and nodes
原 owner 新增節點 移除節點 需要 remap 的 key 不受影響的 key
穩定狀態:所有 key 依順時針 successor rule 指派給 A、B、C。
Key Hash point 原 owner 新 owner 狀態
一句話答法看 remap 數量時要問「變動節點接手或釋放哪段 ring range」,不是問整個 cluster 有多少 key。
06

面試取捨與設計檢查表

回答題目時要把 ring、vnode、replica 與 operational safety 一起講清楚。

面試中提到一致性雜湊時,不要只畫一個環。更完整的回答應該說明它解決了什麼、沒有解決什麼,以及你如何把它放進可運維的系統裡。

面向 應該說清楚 常見追問
Correctness 所有 client 需要使用同一版 ring,否則同一 key 可能被送到不同 owner。 ring version 如何發布?舊版本請求如何相容?
Load Balance 少量實體節點會偏斜,需使用 vnode 與容量權重。 如何避免 hot key 打爆單一 node?
Availability 故障節點的 range 要轉交 successor,但資料系統還需要 replica。 副本如何避開同 rack / zone?
Operations 擴縮容要節流搬遷,避免 backfill 與 foreground traffic 互相干擾。 如何監控 remap rate、rebalance lag、cache miss?
風險一致性雜湊無法自動解決 hot key

若某個 key 本身流量巨大,不管它被分到哪個節點都可能形成 hotspot。此時需要 request coalescing、local cache、key splitting、replication read fanout 或應用層降載。

一句話答法高分答案要從「少搬資料」延伸到 vnode 平衡、ring version 一致、replica failure domain 與 rebalance 節流。