企業級負載平衡簡介

發表於2016-01-04

在之前的一篇文章《放好你的密碼 – 從芝麻金融被攻破說起》中,一位讀者在評論中提出了“如果整個過程速度比較慢登入會有問題”這樣一條評論。雖然說我對文章的正確性很有把握,但也仍需要仔細思考是否自己哪裡沒有說清楚。在這個思考過程中,我想起了一個非常值得一說的話題,那就是負載平衡。

在那篇文章中我們說到,要安全地管理好密碼,計算密碼雜湊所使用的迭代次數應該儘可能地大,從而使得單次雜湊計算的速度變長,增加惡意人員破解密碼的難度。反過來,如果有一百個人或者一千個人同時執行登陸操作,那麼這麼繁瑣的雜湊計算將導致登陸伺服器產生“忙不過來”的情況。此時我們就需要使用負載平衡將登陸請求分散到多個登陸伺服器上,以降低單個伺服器的負載。

負載平衡簡介

或許有些讀者仍然對負載平衡這個名詞感到陌生,那麼我們就花一小段篇幅來講解一下到底什麼是負載平衡。

在一個大型網站中,線上使用者有時可能有幾千個甚至上萬個之多。如果一個使用者的請求需要服務使用0.02秒來處理,那麼該服務例項每秒鐘將只能處理50個這樣的請求,每分鐘也只能處理3000個。如果該服務是一個使用者非常常用的功能,如瀏覽網站的產品列表,那麼很顯然單個服務例項已經無法支援該網站的運營。在這種情況下,我們就需要對該服務進行擴容。

擴容主要分為Scale Up和Scale Out兩種,分別對應著增強單個服務的服務能力以及增強服務數量。在某些情況下,Scale Up是一個較為簡單的操作,例如為該服務所在的伺服器新增更大的記憶體。但是任意一個伺服器所能提供的能力實際上都受到其自身物理硬體能力的限制,尤其是具有越高效能的伺服器其單位服務能力的價格越為昂貴,因此我們就需要使用Scale  Out方式將工作量分攤到多個伺服器之中:

就如上圖所顯示的那樣,當伺服器的負載過多而來不及處理的時候,伺服器就處於一種過載的狀態。在該狀態的服務常常會出現響應速度慢甚至無響應的情況。而在執行了Scale Out之後,我們將會使用多個伺服器同時處理使用者的請求。在該解決方案中,我們需要使用一臺特定的裝置將這些請求分發到各個伺服器。該裝置會根據其內部所包含的請求分發邏輯來決定如何對這些請求進行分發,以避免出現單個伺服器過載的情況。這些用來對請求進行分發的裝置實際上就是負載平衡伺服器。

當然,我們不會等到伺服器真正過載了才去解決這個問題。在服務的日常運維中,我們在伺服器的平均負載和峰值負載達到某個特定閾值時就需要考慮是否需要為相應服務進行擴容。

一旦一個服務使用了負載平衡系統,那麼它將在高可用性以及擴充套件性上得到極大的增強。這也是我們使用負載平衡解決方案的最重要原因。例如對於一個擁有三臺伺服器的負載平衡系統,如果其中一臺發生了故障,那麼負載平衡伺服器可以通過向各個服務傳送心跳等方式得知它們的異常,進而不再向這個發生了故障的伺服器分發任務:

而如果當前負載平衡系統中所負擔的服務容量已經超過了閾值,那麼我們可以簡單地通過在負載平衡系統中新增伺服器來解決這個問題:

這樣,每個伺服器所需要處理的任務就相對減少了,從而減輕了單個伺服器的負擔。

基於DNS的負載平衡

OK,在瞭解了負載平衡系統的大致組成及使用方式之後,我們就來看看各種負載解決方案。

當前業界中所最常使用的負載平衡解決方案主要分為三種:基於DNS的負載平衡,L3/4負載平衡,也即是基於網路層的負載平衡,以及L7負載平衡,即基於應用層的負載平衡。在這些解決方案中,基於DNS的負載平衡是最簡單的,也是最早出現的一種負載平衡解決方案。

當我們通過在瀏覽器的位址列中鍵入域名來訪問某個網站時,瀏覽器將首先查詢本地的DNS快取是否擁有該域名所對應的IP地址。如果有,那麼瀏覽器將嘗試直接使用該IP地址訪問該網站的內容。如果本地DNS快取中沒有該域名所對應的IP地址,那麼它將向DNS傳送一個請求,以獲得該域名所對應的IP並新增到本地DNS快取中。

而在DNS中,一個域名可能和多個IP地址繫結。在這種情況下,DNS響應將會按照Round Robin方式返回這些IP地址的列表。例如在多次通過nslookup或host等命令來檢視特定域名所對應的IP時,其可能的返回如下(因國內網路原因,您需要FQ再進行試驗):

可以看到,不同的DNS請求所返回的結果會按照Round Robin進行輪換,進而使得不同的使用者訪問不同的IP地址,平衡各個伺服器的負載。

雖然這種負載平衡解決方案非常容易實現,但是它有一個致命的缺點:為了減少DNS請求的次數以提高訪問效率,瀏覽器常常快取了DNS查詢的結果。如果一個IP處的服務失效,那麼瀏覽器可能仍會根據DNS快取中所記錄的資訊向該不可用的服務傳送請求(不同的瀏覽器可能有不同的行為)。雖然說整個服務只有一處IP所對應的服務失效了,但是從使用者的角度看來該網站已經不可訪問。因此基於DNS的負載平衡方案並不能作為一個獨立的負載平衡解決方案來提供高可用性的保障,而是作為其它負載平衡解決方案的補充方案來使用。

L3/4 負載平衡

另一種較為常見的負載平衡則是L3/4負載平衡。這裡的L3/4實際上指的就是負載平衡伺服器會根據OSI模型中的第三層網路層(Network Layer)和第四層傳輸層(Transport Layer)所包含的資料來進行負載平衡操作。在這種負載平衡伺服器中,這些資料主要包含資料包的IP頭和TCP、UDP等協議的協議頭:

L3/4負載平衡伺服器的工作原理非常簡單:在資料到達時,負載平衡伺服器將根據自身演算法以及OSI模型三四層所包含的資料決定需要處理該資料的服務例項並將其轉發。

整個負載平衡的執行包含三方面內容:負載平衡伺服器需要知道當前有效的服務例項到底有哪些,並根據自身的分派演算法來決定需要處理資料的服務例項,根據分派演算法的計算結果將資料傳送到目標服務例項上。

首先來看看負載平衡伺服器是如何確定服務例項的有效性的。為了能夠保證從負載平衡伺服器所派發的資料包能被它後面的伺服器叢集正常處理,負載平衡伺服器需要週期性地傳送狀態查詢請求以探測到底哪些服務例項正在有效地工作。這種狀態查詢請求常常會超出很多人的認知:如果服務例項崩潰但是承載它的作業系統正常工作,那麼該作業系統仍然會正常響應負載平衡伺服器所發出的Ping命令,只是此時TCP連線會失敗;如果服務例項並沒有崩潰,而只是掛起,那麼它仍然可以接受TCP連線,只是無法接收HTTP請求。

由於這種狀態查詢請求實際上是特定於服務例項的具體實現,因此很多負載平衡伺服器都允許使用者新增自定義指令碼以執行特定於服務例項的查詢。這些狀態查詢請求常常包含了很多測試,甚至會嘗試從服務例項中返回資料。

一旦負載平衡伺服器發現其所管理的某個服務例項不再有效,那麼它就不會再將任何資料轉發給該服務例項,直到該服務例項迴歸正常狀態。在這種情況下,其它各個服務例項就需要分擔失效伺服器所原本承擔的工作。

這裡需要注意的一點是,在某個服務例項失效以後,整個系統仍應該具有足夠的總容量以處理負載。舉例來說,假如一個負載平衡伺服器管理了三個具有相同能力的服務例項,而且這三個服務例項各自的負載都在80%左右。如果其中一個服務例項失效,那麼所有的負載都需要由其它兩個服務例項來完成。每個服務例項就需要承擔120%的負載,遠超過了它所具有的負載能力。這種情況的直接後果就是,服務顯得非常不穩定,並常常有系統超時,應用無法正常工作的情況出現。

OK。 現在假設我們的負載平衡伺服器有一個設計良好的狀態查詢請求,那麼它就會根據其所使用的負載平衡演算法來為工作的服務例項分配負載。對於初次接觸到負載平衡功能的人來說,最常見的誤區就是認為負載平衡伺服器會根據各個服務例項的響應速度或負載狀況來決定請求需要到達的服務例項。

通常情況下,Round Robin演算法是最常用也是表現最好的負載平衡演算法。如果各個服務例項的容量並不相同,那麼負載平衡伺服器會使用Weighted Round Robin演算法,即根據各個服務例項的實際能力來安比例地分配負載。在某些商業負載平衡伺服器中,其的確會根據當前服務例項的負載以及響應時間等因素對這些分配比例自動進行微小地調整,但是它們並不是決定性的因素。

如果單純地使用Round Robin演算法,那麼具有關聯關係的各個請求將可能被分配到不同的服務例項上。因此很多負載平衡伺服器允許根據資料的特定特徵對這些負載進行分配,如使用一種雜湊演算法來對使用者所在的IP進行計算,並以計算結果決定需要分配到的服務例項。

同樣地,我們也需要考慮某個伺服器例項失效的情況。如果負載平衡系統中的某個伺服器例項失效,那麼雜湊演算法中的雜湊值空間將發生變化,進而導致原本的服務例項分配結果將不再有效。在這種情況下,所有的請求將重新分配伺服器例項。另外,在某些情況下,使用者的IP也可能在各個請求之間發生變化,進而導致它所對應的服務例項發生更改。當然,不用擔心,後面對L7負載平衡伺服器的講解會給您一個解決方案。

在確定了資料包的目標地址後,負載平衡伺服器所需要做的事情就是將它們轉發到目標例項了。負載平衡伺服器所使用的轉發方式主要分為三種:Direct routing,Tunnelling以及IP address translation。

在使用Direct routing方式的時候,負載平衡伺服器以及各個服務例項必須在同一個網段上並使用同一個IP。在接收到資料的時候,負載平衡伺服器將直接對這些資料包進行轉發。而各個服務例項在處理完資料包之後可以將響應返回給負載平衡伺服器,也可以選擇將響應直接傳送給使用者,而不需要再經過負載平衡伺服器。後一種返回方式被稱為Direct Server Return。其執行方式如下所示:

在該過程中,負載平衡伺服器和各個服務例項都不需要對IP(Internet Protocol)層資料進行任何更改就可以對其進行轉發。使用這種轉發方式的負載平衡伺服器的吞吐量非常高。反過來,這種組織方式也要求叢集的搭建人員對TCP/IP等協議擁有足夠多的理解。

另一種轉發方式Tunnelling實際上與Direct routing類似。唯一的一點不同則是在負載平衡伺服器和各個服務之間建立了一系列通道。軟體開發人員仍然可以選擇使用Direct Server Return來減輕負載平衡伺服器的負載。

IP Address Translation則與前兩種方式非常不同。使用者所連線的目標地址實際上是一個虛擬地址(VIP,Virtual IP)。而負載平衡伺服器在接到該請求的時候將會將其目標地址轉化為服務例項所在的實際地址(RIP,Real IP),並將源地址更改為Load Balancer所在的地址。這樣在對請求處理完畢後,服務例項將會把響應傳送到負載平衡伺服器。此時負載平衡伺服器再將響應的地址更改為VIP,並將該響應返回給使用者。在這種轉發方式下,其執行流程則如下所示:

有些細心的讀者會問:在訊息傳遞的過程中,使用者所在的User IP已經不在訊息中存在了,那負載平衡伺服器在傳回響應的時候應該如何恢復使用者的IP地址呢?實際上在這種轉發方式中,負載平衡伺服器會維持一系列會話,以記錄每個經由負載平衡伺服器的正在處理的各個請求的相關資訊。但是這些會話非常危險。如果將會話持續的時間設定得比較長,那麼在一個高併發的負載平衡伺服器上就需要維護過多的會話。反之如果將會話持續的時間設定得過短,那麼就有可能導致ACK Storm發生。

先看會話持續時間較長的情況。假設當前負載平衡伺服器每秒鐘會接收到50000個請求,而且該負載平衡伺服器的會話過期時間為2分鐘,那麼其就需要保持6000000個會話。這些會話會佔用負載平衡伺服器的很大部分資源。而且在負載高峰期,其所消耗的資源可能會成倍地增長,會向伺服器施加更多的壓力。

但是將會話持續時間設定得比較短則更為麻煩。這會導致使用者和負載平衡伺服器之間產生ACK Storm,佔用使用者和負載平衡伺服器的大量頻寬。在一個TCP連線中,客戶端和服務端需要通過各自的Sequence Number來進行溝通。如果負載平衡伺服器上的會話快速地失效,那麼其它TCP連線就有可能重用該會話。被重用的會話中客戶端和服務端的Sequence Number都會被重新生成。如果此時原有的使用者再次傳送訊息,那麼負載平衡伺服器將通過一個ACK訊息通知客戶端其擁有的Sequence Number出錯。而在客戶端接受到該ACK訊息之後,其將向負載平衡伺服器傳送另一個ACK訊息通知服務端所擁有的Sequence Number出錯。服務端接受到該ACK訊息後,將再次傳送ACK訊息到客戶端通知其所擁有的Sequence Number出錯……這樣客戶端和服務端之間就將持續地傳送這種無意義的ACK訊息,直到某個ACK訊息在網路傳輸過程中丟失為止。

因此乍一看來,使用IP Address Translation的方案是最容易的,但是相較於其它兩種方案,它卻是最危險也是維護成本最高的一種方案。

L7 負載平衡

另一種較為常用的負載平衡解決方案則是L7負載平衡。顧名思義,其主要通過OSI模型中的第七層應用層中的資料決定如何分發負載。

在執行時,L7負載平衡伺服器上的作業系統會將接收到的各個資料包組織成為使用者請求,並根據在該請求中所包含的的資料來決定由哪個服務例項來對該請求進行處理。其執行流程圖大致如下所示:

相較於L3/4負載平衡服務所使用的資料,L7負載平衡服務所使用的應用層資料更貼近服務本身,因此其具有更精確的負載平衡行為。

在前面對L3/4負載平衡的講解中我們已經介紹過,對於某些具有關聯關係的各個請求,L3/4負載平衡伺服器會根據某些演算法(如計算IP的雜湊值)來決定處理該請求的服務例項。但是這種方法並不是很穩定。當一個服務例項失效或使用者的IP發生變化的時候,使用者與服務例項之間的對應關係就將發生改變。此時使用者原有的會話資料在新的服務例項上並不存在,進而導致一系列問題。

其實產生這個問題的最根本原因就是使用者與服務例項之間的關聯關係是通過某些外部環境建立的,而並非由使用者/服務例項本身來管理。因此它不能抵禦外部環境變化的衝擊。如果要在使用者和服務例項之間建立穩定的關聯關係,那麼就需要一種穩定的在使用者和服務例項之間傳遞的資料。在Web服務中,這種資料就是Cookie。

簡單地說,基於Cookie的負載平衡服務實際上就是分析使用者請求中的某個特定Cookie並根據其值決定需要分發到的目標地址。其主要分為兩種方式:Cookie  Learning以及Cookie Insertion。

Cookie Learning是不具有侵入性的一種解決方案。其通過分析使用者與服務例項通訊過程中所傳遞的Cookie來決定如何分派負載:在使用者與服務第一次通訊時,負載平衡服務將找不到相應的Cookie,因此其將會把該請求根據負載平衡演算法分配到某個服務例項上。而在服務例項返回的時候,負載平衡伺服器將會把對應的Cookie以及服務例項的地址記錄在負載平衡伺服器中。當使用者再次與服務通訊時,負載平衡伺服器就會根據Cookie中所記錄的資料找到前一次服務該使用者的服務例項,並將請求轉發到該服務例項上。

這麼做的最大缺點就是對高可用性的支援很差。如果一旦負載平衡伺服器失效,那麼在該負載平衡伺服器上所維護的Cookie和服務例項之間的匹配關係將全部丟失。這樣當備份負載平衡伺服器啟動之後,所有的使用者請求都將被定向到隨機的服務例項。

而另一個問題就是會話維護功能對記憶體的消耗。與L3/4伺服器上的會話維護不同,一個Cookie的失效時間可能非常長,至少在一次使用者使用中可能會持續幾個小時。對於一個訪問量達到每秒上萬次的系統而言,負載平衡伺服器需要維護非常多的會話,甚至可能會將伺服器的記憶體消耗殆盡。反過來,如果將負載平衡伺服器中的Cookie過期時間設定得太短,那麼當使用者重新訪問負載平衡伺服器的時候,其將被導向到一個錯誤的服務例項。

除了Cookie Learning 之外,另一種常用的方法就是Cookie Insertion。其通過向響應中新增Cookie以記錄被分派到的服務例項,並在下一次處理請求時根據該Cookie所儲存的值來決定分發到的服務例項。在使用者與伺服器進行第一次通訊的時候,負載平衡伺服器將找不到分派記錄所對應的Cookie,因此其會根據負載平衡演算法為該請求分配一個服務例項。在接收到服務例項所返回的資料之後,負載平衡伺服器將會向響應中插入一個Cookie,以記錄該服務例項的ID。當使用者再次傳送請求到負載平衡伺服器時,其將根據該Cookie裡所記錄的服務例項的ID派發該請求。

相較於Cookie Learning,Cookie Insertion不需要在記憶體中維護Cookie與各個服務例項的對應關係,而且在當前負載平衡伺服器失效的時候,備用負載平衡伺服器也可以根據Cookie中所記錄的資訊正確地派發各個請求。

當然,Cookie Insertion也有缺陷。最常見的問題就是瀏覽器以及使用者自身對Cookie的限制。在Cookie Insertion中,我們需要插入一個額外的Cookie 來記錄分配給當前使用者的服務例項。但是在某些瀏覽器中,特別是移動瀏覽器中,常常會限制Cookie的個數,甚至只允許出現一個 Cookie。為了解決這個問題,負載平衡伺服器也會使用一些其它方法。如Cookie Modification,即修改一個已有的Cookie使其包含服務例項的ID。

而在使用者禁用了Cookie的時候,Cookie Insertion將是完全失效的。此時負載平衡服務所能利用的將僅僅是JSESSIONID等資訊。因此在一個L7負載平衡伺服器中,Cookie Learning和Cookie Insertion常常同時使用:Cookie Learning會在使用者啟用Cookie的時候起主要作用,而在Cookie被使用者禁用的情況下則使用Cookie Learning來根據JSESSIONID來保持使用者與服務例項之間的關聯關係。

或許您會想:L3/4負載平衡伺服器在處理各個關聯請求的時候是通過IP的雜湊值來決定處理該請求的服務例項的。既然這些基於Cookie的解決方案能達到100%的準確,為什麼我們不在L3/4負載平衡伺服器中使用它們呢?答案是:由於L3/4負載平衡伺服器主要關注於資料包級別的轉發,而Cookie資訊則藏匿於資料包之中,因此L3/4負載平衡伺服器很難決定單一的資料包該如何轉發。

例如在執行Cookie Insertion操作的時候,原有資料包中的所有資料都將被後移。此時需要負載平衡伺服器接收到所有資料包之後才能完成:

試想一下接收所有資料包所可能發生的事情吧。在網路的一端傳送多個資料包的時候,網路的另一端所接收到的資料包的順序可能與原有的傳送順序並不一致。甚至在網路擁堵的時候,某些資料包可能會丟失,進而再次加長接收到所有資料包所需要的時間。

因此相較於將資料包直接轉發的方法,等待所有的資料包到齊然後再插入Cookie的效能非常差。在後面對於解決方案的講解中您會看到,L3/4負載平衡伺服器對於效能的要求一般來說是很高的,而L7負載平衡伺服器則可以通過一個叢集來解決自身的效能問題。基於DNS的負載平衡,L3/4負載平衡伺服器以及L7負載平衡伺服器常常協同工作,以組成一個具有高可用性以及高可擴充套件性的系統。

SSL Farm

在上面的講解中,我們忽略了一個事情,那就是L7負載平衡伺服器對於SSL的支援。在L7負載平衡伺服器中,我們常常需要讀寫請求及響應中的Cookie。但是如果通訊使用的是SSL連線,那麼L7負載平衡伺服器將無法對請求及響應的內容進行讀寫操作。

解決該問題所曾經使用的一個解決方案就是:將負載平衡伺服器以反向代理的方式使用。在這種方案中,負載平衡伺服器將擁有服務的證書,並可以通過證書中的金鑰對請求進行解密。解密完成後,負載平衡伺服器就可以開始嘗試讀取Cookie中的內容並根據其所記錄的資訊決定該請求所需要派發到的服務例項。在對該請求進行派發的時候,負載平衡伺服器可以不再使用SSL連線,進而使得各個服務例項不再需要再次解密請求,提高服務例項的執行效率。

在請求處理完畢之後,服務例項將通過服務例項與負載平衡伺服器的非SSL連線返回一個響應。在負載平衡伺服器接收到該響應之後,其將會把該響應加密並通過SSL連線發出:

但是這樣做的問題在於,如果所有對SSL的處理都集中在L7負載平衡伺服器上,那麼它將會變成系統的瓶頸。繞過該問題的方法就是在L7負載平衡伺服器之前使用一系列反向代理來負責SSL的編解碼操作。

此時整個系統的架構將呈現如下的層次結構:

從上圖中可以看到,整個解決方案分為了四層。在使用者的請求到達了第一層的負載平衡伺服器時,其將會把該請求根據自身的負載平衡演算法轉發給處於第二層的專門負責SSL編解碼工作的反向代理。該代理會將傳入的由SSL連線所傳輸的請求由非SSL連線傳出。在請求到達第三層時,L7負載平衡伺服器可以直接訪問這些請求所包含的Cookie,並根據Cookie中的內容決定需要處理該請求的服務例項。

這麼做的好處有很多。首先就是這些反向代理非常便宜,甚至只有常見負載平衡伺服器的1/20左右的價格,卻在處理SSL連線上擁有幾乎相同的效率。除此之外,這些反向代理還提供了非常良好的擴充套件性和高可用性。一旦負載平衡系統在處理SSL連線的能力上顯得有些吃力,我們就隨時可以向系統中新增新的反向代理。而一旦其中一個反向代理失效,那麼其它反向代理可以通過多承擔一些負載來保證系統的安全執行。

需要考慮的問題

在提出具體的負載平衡解決方案之前,我們需要首先講解一下在設計負載平衡系統時我們所需要考慮的一些事情。

首先要說的就是要在負載平衡系統設計時留意它的高可用性及擴充套件性。在一開始的講解中,我們就已經提到過通過使用負載平衡,由眾多伺服器例項所組成的服務具有很高的可用性及擴充套件性。當其中一個服務例項失效的時候,其它服務例項可以幫助它分擔一部分工作。而在總服務容量顯得有些緊張的時候,我們可以向服務中新增新的服務例項以擴充套件服務的總容量。

但是由於所有的資料傳輸都需要經過負載平衡伺服器,因此負載平衡伺服器一旦失效,那麼整個系統就將無法使用。也就是說,負載平衡伺服器的可用性影響著整個系統的高可用性。

解決這個問題的方法要根據負載平衡伺服器的型別來討論。對於L3/4負載平衡伺服器而言,為了能夠讓整個系統不失效,業界中的常用方法是在系統中使用一對負載平衡伺服器。當其中一個負載平衡伺服器失效的時候,另一個還能夠為整個系統提供負載平衡服務。這一對負載平衡伺服器可以按照Active-Passive模式使用,也可以按照Active-Active模式使用。

在Active-Passive模式中,一個負載平衡伺服器處於半休眠狀態。其將會通過向另外一個負載平衡伺服器傳送心跳訊息來探測對方的可用性。當正在工作的負載平衡伺服器不再響應心跳的時候,那麼心跳應用將會把負載平衡伺服器從半休眠狀態喚醒,接管負載平衡伺服器的IP並開始執行負載平衡功能。

而在Active-Active模式中,兩臺負載平衡伺服器會同時工作。如果其中一臺伺服器發生了故障,那麼另一臺伺服器將會承擔所有的工作:

可以說,兩者各有千秋。相較而言,Active-Active模式具有較好的抵抗訪問量大幅波動的情況。例如在通常情況下,兩個伺服器的負載都在30%左右,但是在服務使用的高峰時間,訪問量可能是平時的兩倍,因此兩個伺服器的負載就將達到60%左右,仍處於系統可以處理的範圍內。如果我們使用的是Active-Passive模式,那麼平時的負載就將達到60%,而在高峰時間的負載將達到負載平衡伺服器容量的120%,進而使得服務無法處理所有的使用者請求。

反過來,Active-Active模式也有不好的地方,那就是容易導致管理上的疏忽。例如在一個使用了Active-Active模式的系統中,兩個負載平衡伺服器的負載常年都在60%左右。那麼一旦其中的一個負載平衡伺服器失效了,那麼剩下的唯一一個伺服器同樣將無法處理所有的使用者請求。

或許您會問:L3/4負載平衡伺服器一定要有兩個麼?其實主要由各負載平衡伺服器產品自身來決定的。在前面我們已經講過,實際上探測負載平衡伺服器的可用性實際上需要很複雜的測試邏輯。因此如果一旦我們在一個負載平衡系統中使用了過多的L3/4負載平衡伺服器,那麼這些負載平衡伺服器之間所傳送的各種心跳測試將消耗非常多的資源。同時由於很多L3/4負載平衡伺服器本身是基於硬體的,因此它能夠非常快速地工作,甚至可以達到與其所支援的網路頻寬所匹配的處理能力。因此在一般情況下,L3/4負載平衡伺服器是成對使用的。

如果L3/4負載平衡伺服器真的接近其負載極限,那麼我們還可以通過DNS負載平衡來分散請求:

這種方法不僅僅可以解決擴充套件性的問題,更可以利用DNS的一個特性來提高使用者體驗:DNS可以根據使用者所在的區域選擇距離使用者最近的伺服器。這在一個全球性的服務中尤為有效。畢竟一箇中國使用者訪問在中國架設的服務例項要比訪問在美國架設的服務例項快得多。

反過來由於L7負載平衡伺服器主要是基於軟體的,因此很多L7負載平衡伺服器允許使用者建立較為複雜的負載平衡伺服器系統。例如定義一個具有兩個啟用而有一個備用的一組L7負載平衡伺服器。

講解完了高可用性,我們就來介紹一下負載平衡伺服器的擴充套件性。其實在上面我們剛剛介紹過,L3/4負載平衡伺服器擁有很高的效能,因此一般的服務所使用的負載平衡系統不會遇到需要擴充套件性的問題。但是一旦出現了需要擴充套件的情況,那麼使用DNS負載平衡就可以達到較好的擴充套件性。而L7負載平衡則更為靈活,因此擴充套件性更不是問題。

但是一個負載平衡系統不可能都是由L3/4負載平衡伺服器組成的,也不可能只由L7負載平衡伺服器組成的。這是因為兩者在效能和價格上都具有非常大的差異。一個L3/4負載平衡伺服器實際上價格非常昂貴,常常達到上萬美元。而L7負載平衡伺服器則可以使用廉價伺服器搭建。L3/4負載平衡伺服器常常具有非常高的效能,而L7負載平衡伺服器則常常通過組成一個叢集來達到較高的整體效能。

在設計負載平衡系統時,還有一點需要考慮的那就是服務的動靜分離。我們知道,一個服務常常由動態請求和靜態請求共同組成。這兩種請求具有非常不同的特點:一個動態請求常常需要大量的計算而傳輸的資料常常不是很多,而一個靜態的請求常常需要傳輸大量的資料而不需要太多的計算。不同的服務容器對這些請求的表現差異很大。因此很多服務常常將其所包含的服務例項分為兩部分,分別用來處理靜態請求和動態請求,並使用適合的服務容器提供服務。在這種情況下,靜態請求常常被置於特定的路徑下,如“/static”。這樣負載平衡伺服器就可以根據請求所傳送到的路徑而將動態請求和靜態請求進行適當地轉發。

最後要提到的就是L3/4負載平衡伺服器的一個軟體實現LVS(Linux Virtual Server)。相較於硬體實現,軟體實現需要做很多額外的工作,例如對資料包的解碼,為處理資料包分配記憶體等等呢個。因此其效能常常只是具有相同硬體能力的L3/4負載平衡伺服器的1/5到1/10。鑑於其只具有有限的效能但是搭建起來成本很低,如利用已有的在Lab裡閒置的機器等,因此其常常在服務規模不是很大的時候作為臨時替代方案使用。

負載平衡解決方案

在文章的最後,我們將給出一系列常見的負載平衡解決方案,以供大家參考。

在一般情況下,一個服務的負載常常是通過某些方式逐漸增長的。相應地,這些服務所擁有的負載平衡系統常常是從小到大逐漸演化的。因此我們也將會按照從小到大的方式逐次介紹這些負載平衡系統。

首先是最簡單的包含一對L7負載平衡伺服器的系統:

如果服務的負載逐漸增大,那麼該系統中唯一的L7負載平衡伺服器很容易變成瓶頸。此時我們可以通過新增一個SSL Farm以及執行LVS的伺服器來解決問題:

如果我們還要應對增長的負載,那麼就需要使用真正的基於硬體的L3/4負載平衡伺服器來替代LVS,並增加各層的容量:

由於該解決方案的下面三層基本都有理論上無限的擴充套件性,因此最容易出現過載的就是最上面的L3/4負載平衡伺服器。在這種情況下,我們就需要使用DNS來分配負載了:

相關文章