循序漸進講解負載均衡vivoGateway(VGW)

語言: CN / TW / HK

作者:vivo 互聯網運維團隊- Duan Chengping

在大規模業務場景中,已經不可能通過單機提供業務,這就衍生出了負載均衡的需求。為了滿足合適可靠的負載,本文將從簡單的基礎需求出發,一步步推進並解釋如何建立負載均衡平台。

一、怎麼保證你的業務可靠

想一個問題:假設你有10台服務器對外提供相同的服務,你如何保證這10台服務器能穩定處理外部請求?

這裏可能有很多種解決方案,但本質上都是處理下述兩個問題:

① 客户端的請求應該分配去哪一台服務器比較好?

② 萬一其中某些服務器故障了,如何隔離掉故障服務器?

問題① 處理不好,可能會導致10台服務器中的一部分服務器處於飢餓狀態,沒有被分配客户端請求或者是分配得很少;而另一部分則一直在處理大量的請求,導致不堪負重。

問題② 處理不好,則CAP原則中的可用性(A)可能就沒法保證,除非系統不需要A。

要解決上述問題,你必須實現一套控制器,能調度業務請求和管理業務服務器。很不幸的是,大多數情況下這個控制器往往就是整個系統的瓶頸。因為控制系統如果不深入到客户端上,就必須依賴一個集中式的決策機構,這個機構必然要承載所有客户端的請求。這時候你又得去考慮這個控制器的宂餘和故障隔離的問題,這就變得無休無止。

二、業務和控制隔離

那麼,如何解決上述問題?

那就是專業的事情交給專業平台去做,即,我們需要獨立的負載均衡提供上述2點的解決方案。

對於客户端來説,每次請求一個站點,最終都會轉變成對某個IP發起請求。所以只要能控制客户端訪問的IP地址,我們就能控制請求應該落到哪個後端服務器上,從而達到調度效果,這是DNS在做的事情。或者,劫持客户端所有請求流量,對流量重新分配請求到後端服務器上。這個是Nginx、LVS等的處理方式。

圖片

圖1、通過DNS實現負載均衡的效果示意圖

圖2、通過LVS/Nginx實現負載均衡的效果示意圖

這兩個方式都能達到負載均衡的效果。但這裏面有個嚴重的問題,DNS、Nginx、LVS等服務在互聯網時代不可能單機就能提供業務,都是集羣式(也就是有N台服務器組成),那這些集羣的可靠性和穩定性又該如何保證呢?

DNS主要負責域名解析,有一定的負載均衡效果,但往往負載效果很差,不作為主要考慮手段。Nginx提供7層負載均衡,主要靠域名來做業務區分和負載。LVS是4層負載均衡,主要靠TCP/UDP協議、IP地址、TCP/UDP端口來區分服務和負載。

為了解決Nginx、LVS這些負載均衡器集羣的負載均衡及可靠性,我們可以做下述簡單的方案

  1. 業務服務器的負載和可靠性由Nginx保障;

  2. Nginx的負載和可靠性由LVS保障。

上述方案是遵循了業務 <-- 7層負載 <-- 4層負載的邏輯,實際上是在網絡分層模型中的應用層 <-- 傳輸層的做兩級負載。可以看出,其實這個方案是用另一層負載均衡來解決當前層級的負載和可靠性的問題。但這個方案還是有問題,業務和Nginx集羣這兩層的負載和可靠性是有保障了,但LVS集羣這一層的可靠性怎麼辦?

既然我們在網絡分層模型中應用層 <-- 傳輸層做了兩級負載,那有沒有可能做到應用層 <-- 傳輸層 <-- 網絡層的三級負載?很幸運的是,基於IP路由的方式,網絡設備(交換機、路由器)天然具備了網絡層負載均衡功能。

到此,我們可以實現整個負載均衡鏈條:業務 <-- 7層負載(Nginx) <-- 4層負載(LVS) <-- 3層負載(NetworkDevices);

從這裏可以看出,要確保整個負載均衡體系是有效可靠的,必須從網絡層開始構築。處於高層級的業務,可以為低層級的業務提供負載。相對於低層級,高層級的業務都可以認為是低層級業務的控制面,可以交給專業團隊去實現和管理,低層級業務側只需要關注業務本身實現即可。

圖片

圖3、網絡7層模型和LVS、Nginx之間的對應關係

網絡7層分層模型説明:

7、應用層: 支持網絡應用,應用協議僅僅是網絡應用的一個組成部分,運行在不同主機上的進程則使用應用層協議進行通信。主要的協議有:HTTP、FTP、Telnet、SMTP、POP3等。

6、表示層: 數據的表示、安全、壓縮。(實際運用中該層已經合併到了應用層)

5、會話層: 建立、管理、終止會話。(實際運用中該層已經合併到了應用層)

4、傳輸層: 負責為信源和信宿提供應用程序進程間的數據傳輸服務,這一層上主要定義了兩個傳輸協議,傳輸控制協議即TCP和用户數據報協議UDP。

3、網絡層: 負責將數據報獨立地從信源發送到信宿,主要解決路由選擇、擁塞控制和網絡互聯等問題。

2、數據鏈路層: 負責將IP數據報封裝成合適在物理網絡上傳輸的幀格式並傳輸,或將從物理網絡接收到的幀解封,取出IP數據報交給網絡層。

1、物理層: 負責將比特流在結點間傳輸,即負責物理傳輸。該層的協議既與鏈路有關也與傳輸介質有關。

三、如何實現4層負載均衡

上面説過,3層負載由網絡設備天然提供,但實際使用中是和4層負載緊耦合的,一般不獨立提供服務。4層負載可以直接為業務層提供服務,而不依賴7層負載(7層負載主要面向HTTP/HTTPS等業務),所以我們這裏主要針對4層負載來講。

3.1 如何轉發流量

實現負載均衡,言外之意就是實現流量重定向,那麼,首要解決的問題是如何轉發流量。

4個問題要解決:

① 如何把客户端的流量吸引到負載均衡器上?

② 負載均衡器如何選擇合適的後端服務器?

③ 負載均衡器如何將請求數據發送到後端服務器上?

④ 後端服務器如何響應請求數據?

對於①,

解決方案很簡單,給一批後端服務器提供一個獨立的IP地址,我們稱之為Virtual IP(也即VIP)。所有客户端不用直接訪問後端IP地址,轉而訪問VIP。對於客户端來説,相當於屏蔽了後端的情況。

對於②,

考慮到處於低層級的負載均衡通用性,一般不做複雜的負載策略,類似RR(輪詢)、WRR(帶權重輪詢)的方案更合適,可以滿足絕大多數場景要求。

對於③,

這裏的選擇會往往會影響這④的選擇。理想情況下,我們期望是客户端的請求數據應該原封不動的發送到後端,這樣能避免數據包被修改。前面提到的網絡七層分層模型中,數據鏈路層的轉發可以做到不影響更上層的數據包內容,所以可以滿足不修改客户端請求數據的情況下轉發。這就是網絡常説的二層轉發(數據鏈路層,處在七層網絡模型中的第二層),依靠的是網卡的MAC地址尋址來轉發數據。

那麼,假設把客户端的請求數據包當成一份應用數據打包送到後端服務器是不是可行?該方式相當於負載均衡器和後端建立了一個隧道,在隧道中間傳輸客户端的請求數據,所以也可以滿足需求。

上述兩種解決方案中,依賴數據鏈路層轉發的方案稱之為直接路由方式(Direct Route),即DR模式;另一種需要隧道的方案稱之為隧道(Tunnel)模式。DR模式有一個缺點,因為依賴MAC地址轉發,後端服務器和負載均衡器必須在同一個子網中(可以不嚴謹認為是同一個網段內),這就導致了只有和負載均衡服務器在同子網的服務器能接入,不在同一個子網的這部分就沒有使用負載均衡的機會了,這顯然不可能滿足當前大規模的業務。Tunnel模式也有缺點:既然數據轉發依賴隧道,那就必須在後端服務器和負載均衡器之間建立隧道。如何確保不同業務人員能正確在服務器上配置隧道,並且能監控隧道正常運行,難度都很大,需要一套完成的管理平台,言外之意即管理成本過高。

圖片

圖4、DR模式的轉發示意圖,響應流量不會經過負載均衡器

圖片

圖5、Tunnel模式轉發示意圖,和DR一樣,響應流量不會經過負載均衡器

既然都不是很理想,那還有沒有其他方案?

我們期望的是後端服務器不感知前端有負載均衡存在,服務可以放在任何地方,且不做任何過多配置。既然做不到原封不動傳送客户端的數據包,那就代理客户端的請求。也就是對客户端發出來的數據包的源IP和目的IP分別做一次IP地址轉換。

詳細來説,負載均衡器收到客户端A的請求後,自己以客户端的角色向後端服務器發起相同的請求,請求所帶的payload來自於客户端A的請求payload,這樣保證了請求數據一致。

此時,負載均衡器相當於發起了一個新連接(不同於客户端A發起的連接),新建的連接將會使用負載均衡器的IP地址(稱之為LocalIP)作為源地址直接和後端服務器IP通信。

當後端返回數據給負載均衡器時,再用客户端A的連接將數據返回給客户端A。整個過程涉及了兩個連接,對應兩次IP地址轉換,

  • 請求時刻:CIP→VIP, 轉換成了LocalIP→後端服務器IP, 

  • 數據返回時刻:後端服務器IP→ LocalIP, 轉換成了VIP→ CIP。

客户端該過程完全基於IP地址轉發數據,而不是MAC地址,只要網絡可達,數據就可以順暢在客户端和後端之間傳輸。

圖片

圖6、FULLNAT轉發模式

上述方案稱之為FULLNAT轉發模式,也就是實現了兩次地址轉換。顯然,這種方式足夠簡單,不需要後端服務器做任何調整,也不限制後端部署在何處。但也有個明顯問題,那就是後端看到的請求全部來自於負載均衡器,真實的客户端IP信息完全看不到了,這就相當於屏蔽了真實客户端的情況。幸運的是數據中心中絕大多數應用並不需要明確知道真實客户端的IP地址信息,即便是需要真實客户端IP信息,負載均衡器也可以將這部分信息加載在TCP/UDP協議數據中,通過按需安裝一定的插件獲取。

綜合上述幾種方案來説,FULLNAT模式對我們的業務場景(非虛擬化環境)適合度是最佳的。

既然我們打算使用FULLNAT模式, 則④的解決就沒有任何困難了,因為負載均衡器間接充當了客户端角色,後端的數據必然要全部轉發給負載均衡器,由負載均衡器再發給真正的客户端即可。

表1:各種模式之間的優劣勢分析

3.2 如何剔除異常後端服務器

負載均衡一般提供對後端的健康檢查探測機制,以便能快速剔除異常的後端IP地址。理想情況下,基於語義的探測是最好的,能更有效的檢測到後端是不是異常。但這種方式一來會帶來大量的資源消耗,特別是後端龐大的情況下;這還不是特別嚴重的,最嚴重的是後端可能運行了HTTP、DNS、Mysql、Redis等等很多種協議,管理配置非常多樣化,太複雜。兩個問題點加起來導致健康檢查太過於笨重,會大量佔用本來用於轉發數據的資源,管理成本過高。所以一個簡單有效的方式是必要的,既然作為4層負載,我們都不去識別上游業務是什麼,只關注TCP或者UDP端口是不是可達即可。識別上層業務是什麼的工作交給Nginx這類型的7層負載去做。

所以只要定期檢查所有後端的TCP/UDP端口是否開放,開放就認為後端服務是正常的,沒有開放就認為是服務異常,後端列表中剔除,後續將不再往該異常後端轉發任何數據。

那麼如何探測?既然只是判斷TCP端口是不是正常開放,我們只要嘗試建立一個TCP連接即可,如果能建立成功,則表明端口是正常開放的。但是對於UDP來説,因為UDP是無連接的,沒有新建連接的這種説法,但同樣能夠靠直接發送數據來達到探測的目的。即,直接發送一份數據,假設UDP端口是正常開放的,所以後端通常不會做響應。如果端口是沒有開放的,操作系統會返回一個icmp port unreachable狀態,可以藉此判斷端口不可達。但有個問題,如果UDP探測數據包裏帶了payload,可能會導致後端認為是業務數據導致收取到無關數據。

3.3 如何實現負載均衡器的故障隔離

前面我們説過,4層負載均衡依賴於網絡層衡來確保4層負載均衡器之間的負載是平衡的,那麼負載均衡器的故障隔離就是依賴於網絡層來做。

具體如何做?

實際上,我們是依賴於路由的方式來做網絡層的負載均衡,負載均衡集羣中每台服務器都把相同的VIP地址通過路由協議BGP通告給網絡設備(交換機或者路由器),網絡設備收到相同的一個VIP來自不同的服務器,就會形成一個等價路由(ECMP),言外之意就是形成負載均衡。所以如果我們想要隔離掉某台負載均衡器的話,只要在該服務器上把通過BGP路由協議發佈的VIP撤銷即可,這樣,上游交換機就認為該服務器已經被隔離,從而不再轉發數據到對應設備上。

圖片

圖7、通過撤銷VIP路由進行故障隔離

至此,我們已經實現了一個基於FULLNAT,主要依賴三層協議端口做健康檢查,通過BGP和網絡設備對接實現VIP收發的方式實現負通用載均衡架構模型。

四、VGW的實現方案

基於上述4層負載的架構,我們建設了VGW(vivo Gateway),主要對內網和外網業務提供4層負載均衡服務。下面我們將從邏輯架構、物理架構、宂餘保障、如何提高管理性轉發性能等方面進行説明。

4.1 VGW組件

VGW核心功能是複雜均衡,同時兼具健康檢查,業務引流等功能,所以組成VGW的組件主要就是核心的負載均衡轉發模塊、健康檢查模塊、路由控制模塊。

  1. 負載均衡轉發模塊:主要負責負載計算和數據轉發;

  2. 健康檢查模塊:主要負責檢測後端(RealServer)的可用狀態,並及時清除不可用後端,或者恢復可用後端;

  3. 路由控制模塊:主要進行VIP發佈引流和隔離異常VGW服務器。

4.2 邏輯架構方案

為了方便理解,我們把客户端到VGW環節的部分稱之為外部網絡(External),VGW到後端(RealServer)之間的環節較內部網絡(Internal)。從邏輯架構上將,VGW功能很簡單,就是把External的業務請求均勻分發到Internal的RealServer上。

圖片

圖8、VGW邏輯示意圖

4.3 物理架構方案

物理架構上,對於提供內網的VGW和外網的VGW會有一定差異。

外網VGW集羣使用了至少2張網卡,分別接外網側網絡設備和內網側網絡設備。對於VGW服務器來説,兩個網口延伸出兩條鏈路,類似人的一雙手臂,所以稱這種模式為雙臂模式,一個數據包只經過VGW服務器的單張網卡一次。

圖片

圖9、外網VGW物理示意圖

而內網VGW和外網不同,內網VGW則只用使用了1張網卡,直接內網側網絡設備。相對應的,該方式稱為單臂模式,一個數據包需要需要先從僅有的一張網卡進,然後再從網卡出,最後轉發到外部,總計穿過網卡2次。

圖片

圖10、內網VGW物理示意圖

4.4 VGW現有業務模型

前面我們説過,負載均衡可以為7層負載提供更上一層的負載均衡。

  • 當前VGW最大的業務流量來自於7層接入接出平台(也就是Nginx)的流量,而Nginx基本承載了公司絕大部分核心業務。

  • 當然Nginx並不能支持所有類型的業務,直接構建於TCP、UDP之上的非HTTP類型這一部分流量7層Nginx並不支持,這類業務直接有VGW將數據轉發至業務服務器上,中間沒有其他環節,比如kafka、Mysql等等。

  • 還有一部分業務是用户自建了各種代理平台,類似於Nginx等等,但也由VGW為其提供4層負載均衡。

圖片

圖11、VGW業務模型圖

4.5 VGW的宂餘保障

為了提高可用性,那麼考慮哪些風險?大家自然而然的考慮到服務器故障、進程故障等場景。但VGW場景需要考慮更多,因為VGW整個系統包括了鏈路、網絡設備、服務器、進程等。而且還不能只考慮設備宕機這種簡單的場景,實際上,上述任何設備出現不宕機但轉發異常的情況才是最麻煩的。

所以監控VGW服務轉發是不是正常是第一步,我們通過布放在不同機房和地區的探測節點定期和VIP建立連接,通過建立連接的失敗比列來作為衡量VGW是不是正常的標準。實際上,該監控相當於檢測了整個涉及VGW的所有環節的鏈路和轉發是不是良好的。

上面的監控覆蓋到的是集羣級別的探測,當然我們也建立了其他更細粒度的監控來發現具體問題點。

在有監控一手數據之後,我們就能提供服務器級別故障處理和集羣級別的故障處理能力。

  1. 所有設備級別的直接宕機,VGW做到自動隔離;

  2. 所有鏈路級別的異常,一部分能自動隔離;

  3. 所有進程級別的異常,能達到自動隔離;

  4. 其他非完全故障的異常,人工介入隔離。

服務器級別故障隔離是將某一些VGW服務器通過路由調整將取消VIP的發佈,從而達到隔離目的;

集羣級別故障隔離是將整個VGW集羣的VIP取消發佈,讓流量自動被備用集羣牽引,由備用集羣接管所有業務流量。

圖12、服務器、鏈路級別故障隔離

圖13、集羣級別的故障隔離

4.6 如何提高VGW性能

隨着業務量級越來越大,VGW單機要接收近百萬的QPS請求,同時要達到500W/s以上的包處理能力。顯然一般服務器根本無法達到這麼大量的請求和包處理速度,主要原因在於Linux的網絡處理機制。網絡數據包都必須經過Linux內核,網卡收到數據包後都要發送中斷給CPU,由CPU在內核處理,然後再拷貝一份副本給應用程序。發送數據也要經過內核進行處理一遍。頻繁的中斷、用户空間和內核空間之間不斷的拷貝數據,導致CPU時長嚴重被消耗在了網絡數據處理上,包速率越大,性能越差。當然還有其他諸如Cache Miss,跨CPU的數據拷貝消耗等等問題。

這裏會很容易想到,能不能把上面CPU乾的這些髒活累活扔網卡去幹了,CPU就純粹處理業務數據就行。目前很多方案就是根據這個想法來的,硬件方案有智能網卡,純軟件方案當前用的較多的是DPDK(Intel Data Plane Development Kit)。顯然智能網卡的成本會偏高,而且還在發展階段,應用有一定成本。而DPDK純軟件來説在成本和可控方面要好得多,我們選擇了DPDK作為底層的包轉發組件(實際上是基於愛奇藝開源的DPVS的二次開發)。

DPDK主要是攔截了內核的包處理流程,將用户數據包直接上送至應用程序中,而不是由內核進行處理。同時擯棄了依靠網卡中斷的方式處理數據的行為,轉而採用輪詢的方式從網卡中讀取數據,從達到降低CPU中斷的目的。當然還利用了CPU親和性,使用固定的CPU處理網卡數據,減少進程的切換消耗。另外還有很多關於Cache、內存等方面的優化技術。總體上能夠將服務器網卡包處理速度達到千萬PPS,極大提升網卡的包處理能力,進而能提升服務器的CPS(每秒新建連接數目)。當前我們在100G網卡下,能夠達到 100w+的CPS和1200w+PPS的業務處理量(有限條件下的測試結果,非理論值)。

圖片

圖14、VGW使用的底層工具DPVS(DPDK+LVS)對比幾種現有的負載均衡方案性能

五、總結

通過上述講解,我們逐步從一個業務可靠性的需求推演出一套可行的負載均衡方案,同時結合vivo的實際需求,落地了我們的VGW負載均衡接入平台。當然,當前的負載均衡方案都是大量取捨後的結果,不可能做到完美。同時我們未來還面臨着新的業務協議支持的問題,以及數據中心去中心化的業務模型對負載均衡的集中式控制之間衝突的問題。但技術一直在進步,但總會找到合適的方案的!