Memcached 客戶端支援多伺服器叢集,核心演算法在 SockIOPool.GetSock(key, hashCode) 中。本文剖析 .NET 客戶端 (Enyim/BeIT 等) 的分發邏輯、權重機制與遷移難題。
int hv = (hashCode != null) ? (int)hashCode : ComputeHash(key); // 三種哈希算法可選
int bucket = hv % _buckets.Count; // 核心:哈希值對伺服器列表長度取模
if (bucket < 0) bucket += _buckets.Count; // 處理負數
SockIO sock = GetConnection((string)_buckets[bucket]);
_buckets 非去重列表_buckets 為 ArrayList,**允許重複伺服器地址**pool.SetWeights(new int[] {5, 2}) → 伺服器 A 出現 5 次,B 出現 2 次_buckets 中出現次數| 算法枚舉 | 實現 | 特點 |
|---|---|---|
HashingAlgorithm.Native | key.GetHashCode() | .NET 內建,版本間可能不一致 |
HashingAlgorithm.OldCompatibleHash | 自定義舊版相容算法 | 跨版本穩定,舊客戶端相容 |
HashingAlgorithm.NewCompatibleHash | 自定義新版相容算法 | 分佈更均勻,推薦新專案使用 |
鐵律:叢集內所有客戶端**必須使用相同哈希算法與相同伺服器列表順序**,否則會導致資料讀寫錯位、重複緩存。
int tries = 0;
while (tries++ <= _buckets.Count) {
int bucket = hv % _buckets.Count;
SockIO sock = GetConnection(_buckets[bucket]);
if (sock != null) return sock;
if (!_failover) return null; // 不故障轉移則放棄
// 重新哈希:在 key 前加入嘗試次數,改變 hv
hv += ComputeHash("" + tries + key);
}
return null;
_buckets.Count 次(遍歷所有伺服器)String[] serverlist = { "192.168.1.2:11211", "192.168.1.3:11211" };
int[] weights = { 5, 2 }; // 伺服器1權重5,伺服器2權重2
SockIOPool pool = SockIOPool.GetInstance();
pool.SetServers(serverlist);
pool.SetWeights(weights); // 內部展開 _buckets:5次IP1 + 2次IP2
pool.Initialize();
4 台伺服器 A/B/C/D 運行數月,C 突然宕機。客戶端仍按 4 台算法計算,原映射到 C 的 Key 全部失效,必須回源 DB。若移除 C 重新初始化,**所有 Key 映射關係改變**,A/B/D 上現有緩存全部失效,引發緩存雪崩。
Namespace.Entity.Id(如 Sns.UserLogs.Log.999999)
// 概念代碼
if (key.StartsWith("Sns.UserLogs")) return serverGroupA;
if (key.StartsWith("Sns.Photos")) return serverGroupB;
雖本文未提及,但現代 Memcached/Redis 客戶端(如 EnyimMemcached、StackExchange.Redis)均採用 **Consistent Hashing (Ketama 算法)**: