單機資料庫/搜尋引擎遇到瓶頸時,面臨兩條路:
水平擴展核心難題:**資料如何均勻路由到各節點?節點增減時如何最小化資料遷移?**
node = hash(key) % N
當節點數 N 變為 N+1,**幾乎所有鍵的映射關係改變**,導致全量資料遷移 (如 9 個鍵從 2 節點遷移到 3 節點,僅 1 個留在原處)。億級資料遷移需數小時甚至數天,期間系統負載極高、服務不可用。
2^32 虛擬環 (0 ~ 42 億)新節點僅接管「從前一節點到自己」順時針弧段上的資料,**其他節點資料完全不動**。
刪除節點同理:其資料順時針遷移至下一節點。
物理節點少時 (如 3 台),哈希分布不均導致資料傾斜。
解法:每台物理機映射 多個虛擬節點 (如 3 台 × 100 = 300 虛擬節點),資料分布更均勻。
+------------------+ +------------------+ +------------------+
| 寫入/建立節點 | | 讀取/查詢節點 | | 備份/災備節點 |
| (Index Writer) | | (Query Replicas) | | (Backup Replicas)|
+------------------+ +------------------+ +------------------+
| | |
+-----------+-------------+-------------+-----------+
| |
+------v------+ +------v------+
| 一致性哈希 | | 叢集管理服務 |
| 路由器 | | (狀態/告警/熔斷)|
+-------------+ +-------------+
public class ConsistentHashRouter {
private final SortedMap circle = new TreeMap<>();
private final int virtualNodes;
public ConsistentHashRouter(List nodes, int virtualNodes) {
this.virtualNodes = virtualNodes;
for (String node : nodes) addNode(node);
}
private void addNode(String node) {
for (int i = 0; i < virtualNodes; i++) {
long hash = hash(node + "#VN" + i);
circle.put(hash, node);
}
}
public String getNode(String key) {
if (circle.isEmpty()) return null;
long hash = hash(key);
SortedMap tail = circle.tailMap(hash);
long targetHash = tail.isEmpty() ? circle.firstKey() : tail.firstKey();
return circle.get(targetHash);
}
private long hash(String key) {
// MurmurHash3 / FNV1a / MD5 取前 8 bytes
return Hashing.murmur3_32().hashUnencodedChars(key).asLong() & 0xffffffffL;
}
}