面試官:微服務(wù)場景下負(fù)載均衡應(yīng)該怎么做?
負(fù)載均衡,這個話題在微服務(wù)架構(gòu)里幾乎無人不曉,也是面試中的常客。我們都知道,它在分布式系統(tǒng)中處于核心位置,任何一次服務(wù)調(diào)用,首先要面對的就是負(fù)載均衡該如何選擇。這自然也讓它成了面試中的必考題。
但可惜的是,即便我們都清楚這是個必考點,卻很難在面試中聊出新意和亮點。多數(shù)候選人的回答,往往只是簡單地羅列幾種自己知道的負(fù)載均衡算法,稍微好一些的,會進(jìn)一步探討不同算法的優(yōu)缺點。坦白說,這樣的回答,并不足以讓面試官在你身上貼上“技術(shù)大佬”的標(biāo)簽。
所以今天,我想換個方式,帶你深入那些算法背后微妙的細(xì)節(jié),并給出一個結(jié)合本地緩存的實戰(zhàn)案例。我的目標(biāo)是,讓你在下次面試中,能真正做到游刃有余,展現(xiàn)出超越大多數(shù)人的技術(shù)深度,給面試官留下深刻的印象。
1. 負(fù)載均衡的核心算法
在深入探討之前,我們必須先對負(fù)載均衡的基礎(chǔ)算法有一個清晰的認(rèn)識。從本質(zhì)上講,負(fù)載均衡就是要解決一個核心問題:“當(dāng)一個請求過來時,我應(yīng)該把它交給集群中的哪一臺服務(wù)器處理?”

理論上,你當(dāng)然希望把請求精準(zhǔn)地發(fā)送給那個能夠最快返回響應(yīng)的節(jié)點。但要做到這一點并不容易。你可能會有些困惑,因為我們常聽到的輪詢、隨機(jī)、哈希等算法,似乎并沒有在“試圖判斷哪個節(jié)點最合適”。
沒錯,這一類算法我們稱之為靜態(tài)負(fù)載均衡算法。它們并不實時感知后端服務(wù)的真實負(fù)載,而是依賴于統(tǒng)計學(xué)意義上的“最優(yōu)”,即假設(shè)在大量請求下,按照既定規(guī)則分配,最終能夠達(dá)到整體的均衡。
與之相對的,是動態(tài)負(fù)載均衡算法。這類算法會實時地、或者準(zhǔn)實時地去探測所有候選節(jié)點的狀態(tài),并從中挑選出當(dāng)前最合適的節(jié)點。典型的例子包括最少連接數(shù)、最少活躍請求數(shù)以及最快響應(yīng)時間算法。
接下來,秀才就帶大家一個一個地來剖析它們。
1.1 靜態(tài)負(fù)載均衡算法
1.1.1 輪詢(Round Robin)與加權(quán)輪詢(Weighted Round Robin)
輪詢是最簡單、最直觀的一種策略,用一句俗語講,就是“排排坐,分果果”。有A、B、C三臺服務(wù)器,第一個請求給A,第二個給B,第三個給C,第四個再回到A,如此循環(huán)往復(fù),簡單粗暴,主打一個“雨露均沾”。

但現(xiàn)實中,服務(wù)器的性能(CPU、內(nèi)存等)往往并不相同。讓一臺“法拉利”和一臺“拖拉機(jī)”處理同等數(shù)量的請求顯然不合理。因此,加權(quán)輪詢應(yīng)運而生。我們可以根據(jù)服務(wù)器的性能配置賦予不同的“權(quán)重”,性能越好,權(quán)重越高,被分配到的請求就越多,實現(xiàn)“能者多勞”。

1.1.2 深度思考:平滑加權(quán)輪詢
普通的加權(quán)輪詢雖然考慮了權(quán)重,但可能存在一個問題:它可能會在短時間內(nèi)連續(xù)將請求發(fā)往同一高權(quán)重節(jié)點,造成流量突刺。例如,權(quán)重為{A:3, B:1, C:1},那么可能會出現(xiàn) A, A, A, B, C 這樣的序列,對A的瞬時壓力較大。
為了解決這個問題,Nginx等成熟的負(fù)載均衡器引入了更精妙的“平滑加權(quán)輪詢算法”。
該算法的邏輯稍顯復(fù)雜,它為每個節(jié)點維護(hù)兩個權(quán)重:一個是固定的weight(初始權(quán)重),另一個是會動態(tài)變化的current_weight(當(dāng)前權(quán)重)。每次選擇節(jié)點時,執(zhí)行以下步驟:
- 遍歷所有節(jié)點,將其
current_weight的值增加各自的weight。 - 在所有節(jié)點中,選擇
current_weight值最大的那個作為本次的目標(biāo)節(jié)點。 - 將選中節(jié)點的
current_weight減去所有節(jié)點weight的總和。
通過這種方式,一個高權(quán)重節(jié)點在被選中后,它的“當(dāng)前權(quán)重”會大幅下降,從而保證下一次輪到其他節(jié)點,使得流量分配在時間上更加平滑均勻,避免了流量的集中爆發(fā)。
1.1.3 隨機(jī)(Random)與加權(quán)隨機(jī)(Weighted Random)
隨機(jī)算法的邏輯同樣簡單:從可用節(jié)點列表中完全隨機(jī)地抽取一個來處理請求。
而加權(quán)隨機(jī)則是為每個節(jié)點設(shè)置一個被“抽中”的概率,這個概率通常與權(quán)重成正比。權(quán)重越大的節(jié)點,被選中的幾率也越高。

在實踐中,隨機(jī)類算法和輪詢類算法在宏觀上的效果是相似的,但輪詢的可控性相對更強(qiáng)一些,序列是確定的。在很多場景下,它們可以互為替代。
1.1.4 哈希(Hash)與一致性哈希(Consistent Hashing)
哈希算法的思路是,根據(jù)請求里的某個固定參數(shù)(如用戶ID、設(shè)備ID、訂單號等)計算出一個哈希值,再用該值對服務(wù)器數(shù)量取模,從而將同一類請求固定地路由到某一臺服務(wù)器上。

這種方式最大的優(yōu)點是,對于同一標(biāo)識的請求,總能命中同一臺后端服務(wù)器,這對于需要利用本地緩存或者維持會話狀態(tài)的場景來說,非常有用。但它的弊端也很明顯:
- 哈希函數(shù)的選擇:如果哈希函數(shù)設(shè)計不佳,導(dǎo)致計算出的哈希值分布不均,就會造成數(shù)據(jù)傾斜,某些節(jié)點負(fù)載過高。
- 集群伸縮的噩夢:當(dāng)集群擴(kuò)縮容,服務(wù)器數(shù)量
N發(fā)生變化時,mod N的計算結(jié)果會劇烈變動,導(dǎo)致絕大多數(shù)緩存失效,引發(fā)緩存雪崩。
為了攻克這個難題,一致性哈希被設(shè)計出來,它也是面試中的高頻考點。
你可以將其理解為一個0到2^32-1的閉環(huán)哈希環(huán)。它的工作流程如下:
- 節(jié)點映射:將每個服務(wù)器節(jié)點的IP或主機(jī)名進(jìn)行哈希計算,映射到哈希環(huán)上的某個具體位置。
- 請求映射:當(dāng)一個請求到來,根據(jù)其Key(如用戶ID)計算哈希值,同樣映射到環(huán)上的某個位置。
- 節(jié)點查找:從請求映射的位置開始,沿著順時針方向?qū)ふ遥龅降牡谝粋€服務(wù)器節(jié)點,就是該請求的目標(biāo)處理節(jié)點。

打個比方,這就像一個鐘表,朋友約你“下一個整點”在某地見面,你一看表是3:45,那么你自然知道下一個整點是4點。一致性哈希的查找過程與此類似。
它的精妙之處在于,當(dāng)一個節(jié)點下線或上線時,它只會影響到其在哈希環(huán)上逆時針方向相鄰的一小部分請求,而不會導(dǎo)致全局性的映射關(guān)系失效,從而極大地保證了集群伸縮時系統(tǒng)的穩(wěn)定性。
1.2 動態(tài)負(fù)載均衡算法
與靜態(tài)算法不同,動態(tài)算法會實時采集服務(wù)端的負(fù)載數(shù)據(jù),并以此為依據(jù)進(jìn)行“智能”的流量分配。
1.2.1 最少連接數(shù)(Least Connections)
該算法的假設(shè)非常直觀:連接數(shù)越少的服務(wù)器,其當(dāng)前負(fù)載就越低。因此,它會選擇當(dāng)前活躍連接數(shù)最少的節(jié)點來處理新請求。

這個假設(shè)在很多場景下是成立的,但存在一個不容忽視的盲點:連接數(shù)不完全等同于負(fù)載壓力。尤其在如今廣泛采用連接多路復(fù)用的情況下,一個客戶端可能通過一個連接,連續(xù)發(fā)送10個請求。此時,連接數(shù)并不能真實反映節(jié)點的繁忙程度。一個處理輕量級請求的節(jié)點可能維持著很多連接,但CPU空閑;而另一個節(jié)點可能只有一個連接,卻在處理一個極其耗時的計算任務(wù)。
1.2.2 最少活躍數(shù)(Least Active Requests)
這是對“最少連接數(shù)”的改進(jìn),它關(guān)注的是“活躍請求數(shù)”,即服務(wù)端已經(jīng)接收但尚未處理完成返回響應(yīng)的請求數(shù)量。客戶端會統(tǒng)計每個節(jié)點的活躍請求數(shù),并將新請求發(fā)往數(shù)量最少的那個節(jié)點。

這比看連接數(shù)更能反映節(jié)點的真實負(fù)載,但它同樣無法區(qū)分請求本身的“重量”。一個處理大商家報表的請求,其對資源的消耗可能遠(yuǎn)超十個普通用戶的簡單查詢請求。因此,即便活躍數(shù)相同,兩個節(jié)點的實際負(fù)載也可能天差地別。
1.2.3 最快響應(yīng)時間(Fastest Response Time)
該算法認(rèn)為,節(jié)點的響應(yīng)時間是其綜合處理能力的最佳體現(xiàn),因為它綜合了網(wǎng)絡(luò)延遲、服務(wù)器負(fù)載、任務(wù)處理時長等多種因素。響應(yīng)時間越短,說明節(jié)點當(dāng)前狀態(tài)越好。這里的響應(yīng)時間可以是最近一段時間的平均響應(yīng)時間,也可以是P99、P999等分位線指標(biāo),選擇哪種具體看業(yè)務(wù)場景,效果不會有天壤之別。

這是一個更加精準(zhǔn)和綜合的動態(tài)指標(biāo)。在實現(xiàn)時需要特別注意,統(tǒng)計的響應(yīng)時間應(yīng)具有“時效性”。也就是說,我們應(yīng)該只統(tǒng)計近期請求的響應(yīng)時間,并且越近的響應(yīng)時間,其參考權(quán)重應(yīng)該越高。這個“指標(biāo)隨時間衰減”的思想,在服務(wù)治理的很多領(lǐng)域(如熔斷、限流)都會反復(fù)出現(xiàn),是一個非常重要的概念。
1.2.4 算法小結(jié)與共同挑戰(zhàn)
最少連接數(shù)、最少活躍數(shù)和最快響應(yīng)時間,都可以看作是選擇了單一的指標(biāo)來評估節(jié)點負(fù)載。這個思路可以啟發(fā)我們,在特定場景下設(shè)計自己的負(fù)載均衡算法。比如說在CPU密集型的應(yīng)用里,你可以設(shè)計一個負(fù)載均衡算法,每次篩選CPU負(fù)載最低的節(jié)點。當(dāng)然,這里的難點就變成了你需要考慮怎么高效、低成本地采集到所有服務(wù)端節(jié)點的CPU負(fù)載數(shù)據(jù)。
這些動態(tài)算法還有一個共同的挑戰(zhàn):信息孤島。因為負(fù)載數(shù)據(jù)通常是由客戶端各自采集和決策的。客戶端A并不知道客戶端B向服務(wù)器發(fā)送了多少請求,這導(dǎo)致每個客戶端都像一個“信息孤島”,基于不完整的信息做出的決策可能是片面的,甚至是錯誤的。

如圖所示,客戶端1看到自己與服務(wù)端節(jié)點1的連接數(shù)最少,于是選擇了它。但它并不知道,客戶端2已經(jīng)與節(jié)點1建立了大量的連接,導(dǎo)致節(jié)點1的實際負(fù)載非常高。此時,客戶端1本應(yīng)選擇節(jié)點2。
要解決這個問題,需要建立全局的上帝視角,讓服務(wù)端主動上報狀態(tài)。思路有兩種:
- 響應(yīng)時攜帶:服務(wù)端在返回業(yè)務(wù)數(shù)據(jù)時,通過HTTP Header或RPC的Metadata,將自己當(dāng)前的CPU、內(nèi)存等負(fù)載信息一并返回給客戶端。這需要微服務(wù)框架的支持。

- 統(tǒng)一監(jiān)控平臺:所有服務(wù)端節(jié)點定期向一個中心化的監(jiān)控系統(tǒng)(如Prometheus)上報自己的狀態(tài)數(shù)據(jù)。客戶端在決策前,先向監(jiān)控系統(tǒng)查詢?nèi)肿顑?yōu)節(jié)點。

不過,由于實現(xiàn)復(fù)雜,會引入額外的依賴和延遲,目前業(yè)界大規(guī)模采用的依舊是簡單的靜態(tài)算法,但這并不妨礙我們將這些深度思考作為面試的亮點。
2. 面試亮點與實戰(zhàn)進(jìn)階
掌握了基礎(chǔ)算法,你已經(jīng)及格了。想拿到優(yōu)秀,你需要展現(xiàn)出解決復(fù)雜問題的能力。在準(zhǔn)備面試時,你必須搞清楚自己公司項目里,無論是Nginx網(wǎng)關(guān)還是客戶端負(fù)載均衡,用的是什么算法,并準(zhǔn)備一些相關(guān)的線上事故案例,這會非常有說服力。
2.1 巧妙應(yīng)對“大請求”難題
你可以通過一個你親身經(jīng)歷或精心準(zhǔn)備的線上案例來引出這個話題:
“一般來說,加權(quán)類的算法都需要考慮權(quán)重的設(shè)置和調(diào)整。我們公司線上用的是輪詢,雖然簡單,但因為它沒有實際查詢服務(wù)端節(jié)點的負(fù)載,難免會出現(xiàn)偶發(fā)性的負(fù)載不均衡。”
“我們曾經(jīng)遇到一個問題:線上服務(wù)大部分時間RT都很平穩(wěn),但會偶發(fā)性地出現(xiàn)尖銳的毛刺,且間隔和慢的程度都不固定,非常奇怪。后來經(jīng)過深入排查,發(fā)現(xiàn)罪魁禍?zhǔn)资恰笳埱蟆贁?shù)大客戶的查詢會消耗掉節(jié)點的大量CPU和內(nèi)存。當(dāng)其他普通用戶的請求被輪詢到這個‘倒霉’的節(jié)點時,就會被嚴(yán)重拖慢。”
這個問題暴露了所有靜態(tài)算法的通病:只關(guān)心請求數(shù)量的均衡,而忽略了請求本身的“重量”。
當(dāng)面試官追問解決方案時,你可以從兩個層面回答:
- 業(yè)務(wù)層面(治本):“這個大請求本質(zhì)上是一個大的批量操作。后來我們與業(yè)務(wù)方協(xié)作,對接口進(jìn)行了優(yōu)化,限制了一次最多只能獲取100條數(shù)據(jù),通過產(chǎn)品層面的優(yōu)化解決了這個問題。”
- 架構(gòu)層面(治標(biāo)):“我們對負(fù)載均衡算法做了一點改造。通過用戶畫像系統(tǒng),每天計算出一批大客戶列表,將他們的請求在負(fù)載均衡層通過特定的規(guī)則進(jìn)行識別,并特殊處理,路由到專用的幾個節(jié)點上。雖然大客戶的請求依舊慢,但至少保證了廣大普通用戶不再受他們影響。這本質(zhì)上是服務(wù)治理中‘流量隔離’思想的應(yīng)用。”
隔離的方案更顯技術(shù)深度,可以順勢將話題引向服務(wù)治理。回答完后,可以再拋出一個引子:“負(fù)載均衡有時還能巧妙地解決一些技術(shù)問題,比如緩存。”
2.2 動態(tài)權(quán)重調(diào)整的藝術(shù)
對于加權(quán)類算法,權(quán)重如何設(shè)定和調(diào)整,是另一個體現(xiàn)你思考深度的點。
2.2.1 “成加敗減”原則
權(quán)重不應(yīng)是固定不變的,而應(yīng)根據(jù)節(jié)點的實際服務(wù)質(zhì)量動態(tài)調(diào)整。你可以抓住“成加敗減”的關(guān)鍵詞來展開。
- 調(diào)用成功:說明節(jié)點健康,可以適當(dāng)增加其權(quán)重。這里的“成功”是指網(wǎng)絡(luò)層面調(diào)用成功,即便是業(yè)務(wù)邏輯返回了失敗碼,也算是調(diào)用成功。
- 調(diào)用失敗(如超時、網(wǎng)絡(luò)錯誤):說明節(jié)點可能存在問題,需要降低其權(quán)重,減少后續(xù)流量。可以進(jìn)一步細(xì)化,例如網(wǎng)絡(luò)類錯誤(如EOF)意味著問題更嚴(yán)重,降權(quán)的幅度也應(yīng)該更大;而超時這類錯誤,可能是偶發(fā)性的,降權(quán)幅度可以小一些。

2.2.2 別忘了設(shè)置“安全邊界”
這是一個非常關(guān)鍵的工程實踐經(jīng)驗,也是很多開發(fā)者容易忽略的坑:
“權(quán)重的動態(tài)調(diào)整必須有上下限。權(quán)重的絕對值不重要,不同節(jié)點間的比例才重要。但調(diào)整時必須考慮安全問題。下限不能為0,否則節(jié)點一旦權(quán)重歸零,可能永遠(yuǎn)無法恢復(fù)流量,就‘死’掉了。上限也不能無限高,比如不超過初始權(quán)重的兩三倍,防止因某個節(jié)點表現(xiàn)持續(xù)良好而導(dǎo)致所有流量都涌向它,最終被壓垮。很多公司都因為沒有控制好權(quán)重的上下限而引發(fā)過線上故障。”
最后,你可以將這個話題再度升華:“這種根據(jù)調(diào)用結(jié)果調(diào)整權(quán)重的方式,和在服務(wù)注冊與發(fā)現(xiàn)中暫時剔除故障節(jié)點,本質(zhì)上都是為了提高系統(tǒng)的可用性,是服務(wù)治理的重要一環(huán)。”
2.3 一致性哈希與本地緩存的化學(xué)反應(yīng)
這是一個能充分展示你架構(gòu)設(shè)計能力的微創(chuàng)新方案。
2.3.1 方案的提出與優(yōu)勢
“在性能要求極致的場景,我們會使用本地緩存。但如果配合輪詢等算法,同一個Key的請求會被分散到不同節(jié)點,這將導(dǎo)致幾個嚴(yán)重的問題:嚴(yán)重的緩存未命中、多個節(jié)點緩存同樣的數(shù)據(jù)導(dǎo)致內(nèi)存浪費,以及極其棘手的數(shù)據(jù)一致性問題。”

“在這種情況下,一個很自然的想法就是,能否把同一類請求都讓同一個節(jié)點來處理。于是,我們的解決方案是,將一致性哈希算法與本地緩存結(jié)合。例如,針對用戶的緩存,我們使用用戶ID作為哈希的Key,這樣就可以確保同一個用戶的請求都會被穩(wěn)定地路由到同一節(jié)點上,這極大地提升了本地緩存的命中率,并降低了整體的內(nèi)存消耗。”

2.3.2 為什么說只能緩解,無法根治?
在拋出方案后,主動揭示其局限性,更能體現(xiàn)你的嚴(yán)謹(jǐn)和思考深度。
“需要強(qiáng)調(diào)的是,即便采用了一致性哈希,也只能‘緩解’而不是‘根治’數(shù)據(jù)一致性問題。”
當(dāng)面試官追問時,你就可以給出最后的答案,關(guān)鍵詞是“應(yīng)用發(fā)布”:
“問題出在集群節(jié)點數(shù)量發(fā)生變化的瞬間,比如應(yīng)用發(fā)布或服務(wù)器擴(kuò)縮容時。當(dāng)整個集群的節(jié)點數(shù)量發(fā)生變化時,就難免會導(dǎo)致同樣的數(shù)據(jù)緩存在多個節(jié)點上。

想象這個時序:
- 一個讀請求A(查詢user_id為1的昵稱)根據(jù)當(dāng)時的哈希規(guī)則,命中老節(jié)點N1,但N1本地緩存沒有數(shù)據(jù),于是準(zhǔn)備查詢數(shù)據(jù)庫。
- 此時,集群發(fā)生變更(如上線了新節(jié)點N2),哈希環(huán)結(jié)構(gòu)改變。
- 緊接著,一個寫請求B(將user_id為1的昵稱改為小剛)根據(jù)新的哈希規(guī)則,命中了新節(jié)點N2。N2更新數(shù)據(jù)庫,并刷新了自己的本地緩存為“小剛”。
- 隨后,請求A在老節(jié)點N1上從數(shù)據(jù)庫讀到了舊數(shù)據(jù)“小明”,并寫入了N1的本地緩存。
這就造成了數(shù)據(jù)不一致。雖然這個不一致的窗口期很短,但它確實存在。應(yīng)用發(fā)布是節(jié)點數(shù)量變化最常見的原因,因為發(fā)布過程就可以看作是先下線一個老節(jié)點,再上線一個新節(jié)點。不過,也可以看出,這個方案已經(jīng)將數(shù)據(jù)一致性的問題大大緩解了。”
通過這個方案,你已經(jīng)主動將話題引向了更為復(fù)雜的緩存和數(shù)據(jù)一致性領(lǐng)域,可以為后續(xù)的討論做足鋪墊。
3. 小結(jié):帶著架構(gòu)思維去面試
今天我們系統(tǒng)性地探討了負(fù)載均衡。從基礎(chǔ)的靜態(tài)、動態(tài)算法,到平滑加權(quán)、一致性哈希的深度原理,再到結(jié)合線上案例的“大請求隔離”、“動態(tài)權(quán)重調(diào)整”和“一致性哈希+本地緩存”三大實戰(zhàn)亮點。
記住這些關(guān)鍵詞:大請求、成加敗減、上下限、可用性、應(yīng)用發(fā)布。將這些知識點內(nèi)化,并結(jié)合自己的項目經(jīng)驗,形成一套屬于你自己的、有深度、有亮點的回答體系,你一定能讓面試官刮目相看。





























