負載平衡:先選對機房,再選對機器
· tech
📑 目錄
一個請求從使用者送出到被處理,其實要通過兩層負載平衡,回答兩個不同層次的問題:去哪個資料中心? 進了之後分給哪台機器? 這兩層顧的事情完全不同,把它們分開看,負載平衡就清楚多了。
兩層負載平衡:先選機房,再選機器
前端這層要處理的是「地理與災難」等級的問題:把使用者導到離他近、還活著、且吃得下的機房。常見手段是 DNS、Anycast、VIP——但 DNS 有個先天弱點:它會被層層快取、TTL 沒到不會更新、而且它看不到後端現在健康不健康。所以 DNS 只能做粗粒度的分流,真正細緻的活,留給機房內那一層。
Round Robin 為什麼不夠好
進了機房,最直覺的分法是 Round Robin——請求輪流發給每台機器,雨露均霑。聽起來很公平,但它偷偷假設了三件事,而這三件事在現實裡全是錯的:
所以更好的做法,是讓 backend 主動回報自己的即時使用率,LB 依這個去加權分配(Weighted Round Robin)——忙的少給、弱的少給、不健康的不給。而那個「快速失敗反而吸走流量」的陷阱,本質上就是連鎖失效的一種:壞掉的節點不但沒被隔離,還被獎勵了更多流量。
兩個容易忽略的實務細節
- Subsetting(連線子集):如果每個 client 都跟每一台 backend 建連線,
N × M條連線會爆炸。實務上讓每個 client 只連一個子集——省下大量連線與健康檢查的成本,又不犧牲太多平衡。 - Lame duck(跛鴨)狀態:要下線一台機器時,別直接砍掉——它手上可能還有處理到一半的請求。正確做法是先進入「跛鴨」狀態:告訴 LB 別再送新請求來,但把手上的做完,排空(drain)之後才真正關掉。健康 → 跛鴨(排水)→ 死,而不是一刀砍斷。這跟 K8s 的 readiness probe + 優雅關機是同一套思路。
反思
「平均分配」不等於「平均負載」——這是我踩過的坑
Round Robin 最迷人的地方,是它看起來太公平了——每台輪流拿一個,還有什麼比這更平均?但我自己就吃過虧:早年做一個服務,前面掛 Round Robin,壓測數字很漂亮,上線後卻總有一兩台 CPU 特別高、偶爾還會超時。查了半天才懂,問題不在機器,在我均分錯了東西:我均分的是「請求數」,但有些請求要跑一個很重的查詢、有些秒回,而機器規格其實也有新有舊。數量相等,負載天差地遠。 從那次之後,我對任何「均分」的機制都會多問一句:我分的這個單位,跟我真正想平衡的東西,是同一件事嗎? 分請求數、分連線數、分 partition 數——這些常常都不等於分「真實的工作量」。
壞掉的東西反而吸走流量,是最反直覺的一種故障
「挑最閒的機器」聽起來絕對正確,直到你意識到:一台正在秒速噴錯的機器,在『挑最閒的』眼中,就是最閒的那台——因為它回應快(雖然回的是錯誤)、佇列是空的。於是負載平衡器興高采烈地把流量全導過去,等於親手把所有使用者送進火坑。這個坑讓我學到:健康判斷不能只看「反應快不快」,要看「有沒有真的把事做對」。快速失敗如果沒被正確標記成不健康,比慢還危險——它會偽裝成高效能。這也是為什麼健康檢查(監控)要看的是成功率,而不只是延遲。
優雅退場,是一個系統成不成熟的分水嶺
Lame duck 這個概念我特別有感,因為「怎麼把一台機器安全地拿下來」這件事,看起來很小,卻最能區分一個系統成不成熟。不成熟的系統下線靠一刀砍——手上做到一半的請求全部變成使用者眼中的錯誤;成熟的系統會先擋新的、把舊的做完、排空了才走。我在 K8s 上反覆體會到同一件事:一個 Pod 要被換掉時,得先讓它從 Service 的名單摘除、停止接新流量,再給它一段寬限期收尾。能不能優雅地退場,往往比能不能華麗地上線更能看出功力——因為退場時你面對的是「正在進行中的真實流量」,騙不了人。