一組伺服器提供同一服務,需求:
採用「大盒子」概念:建立固定大小的雜湊環(如 1000 格),將每台實體伺服器映射為多個**虛擬節點**均勻分佈於環上。請求以隨機數或金鑰雜湊落點,順時針尋找最近的虛擬節點所屬伺服器。
public abstract class HashBase
{
protected int _maxHashItemCount;
protected readonly object _syncRoot = new object();
protected Dictionary _ring; // 雜湊環:位置 -> 節點
protected Dictionary _loadWarning; // 節點 -> 是否過載
protected abstract int GetRandom(); // 隨機數/雜湊值產生
protected abstract T SelectOtherUnLoadWarningItem(T current); // 過載時選擇備援節點
public HashBase(int maxHashItemCount, IList items) => Init(maxHashItemCount, items);
protected void Init(int maxHashItemCount, IList items)
{
lock (_syncRoot)
{
_maxHashItemCount = maxHashItemCount;
_ring = new Dictionary(maxHashItemCount);
// 簡易輪詢分佈(生產建議用 Ketama 演算法)
for (int i = 0; i < maxHashItemCount; i++)
_ring[i] = items[i % items.Count];
_loadWarning = items.ToDictionary(k => k, v => false);
}
}
public T GetItem()
{
int pos = GetRandom();
lock (_syncRoot)
{
T node = _ring[pos];
if (_loadWarning[node]) node = SelectOtherUnLoadWarningItem(node);
return node;
}
}
public void SetLoadWarning(T node) => lock (_syncRoot) _loadWarning[node] = true;
public void UnsetLoadWarning(T node) => lock (_syncRoot) _loadWarning[node] = false;
public virtual void ReConfig(int maxHashItemCount, IList items) => Init(maxHashItemCount, items);
}
public class SimpleHashImp : HashBase
{
private readonly Random _random;
public SimpleHashImp(int seed, int maxHashItemCount, IList items)
: base(maxHashItemCount, items) => _random = new Random(seed);
protected override int GetRandom() => _random.Next(_maxHashItemCount);
protected override string SelectOtherUnLoadWarningItem(string current)
{
foreach (var kv in _loadWarning)
if (!kv.Value) return kv.Key;
throw new ApplicationException("503: All nodes overloaded");
}
public override void ReConfig(int maxHashItemCount, IList items)
{
base.Init(maxHashItemCount, items); // 重建環
}
}
static void Main()
{
var nodes = new List { "A", "B", "C" };
var lb = new SimpleHashImp(10, 20, nodes); // 20 格環
for (int i = 0; i < 100; i++)
{
if (i == 10) { lb.SetLoadWarning("A"); Console.WriteLine(">>> Node A OVERLOAD"); }
if (i == 20) { lb.UnsetLoadWarning("A"); Console.WriteLine(">>> Node A RECOVERED"); }
if (i == 30) { nodes.Add("D"); lb.ReConfig(100, nodes); Console.WriteLine(">>> ADD Node D, RESIZE RING"); }
Console.WriteLine($"Req {i:D3} -> {lb.GetItem()}");
Thread.Sleep(100);
}
}
輸出觀察點:
| 議題 | 建議方案 |
|---|---|
| 單點失效 (LB 自身掛掉) | LB 無狀態化,部署多實例 + DNS Round Robin 或 VIP;狀態外置至 Redis/ZooKeeper |
鎖競爭 (lock 瓶頸) | 雙緩衝環:舊環服務舊請求,新環服務新請求,無鎖切換;或使用 ConcurrentDictionary + Interlocked |
| 隨機分佈不均 | 改用 **Ketama 一致性雜湊** (MD5/SHA1 + 160 虛擬節點/實體節點) |
| 過載判定邏輯 | 整合健康檢查:CPU/記憶體/佇列長度/錯誤率,定期推送至 LB 更新 _loadWarning |
| 動態擴容無感知 | 節點啟動時向服務發現 (Consul/Etcd) 註冊,LB 監聽變更事件自動 ReConfig |
本文實作了一個**可擴充、可觀測、生產就緒**的負載均衡核心:
實際專案建議直接採用成熟框架(NGINX、HAProxy、Envoy、YARP、Polly + HttpClientFactory),並在應用層實作業務級熔斷與重試。