前言
在高速發(fā)展的移動互聯(lián)網(wǎng)時代,負載均衡有著舉足輕重的地位,它是應用流量的入口,對應用的可靠性和性能起著決定性的作用,因此負載均衡需要滿足高性能、高可靠兩個特點。TSGW是自研的一款四層負載均衡,主要用于替代原有環(huán)境的四層負載均衡LVS,目前處理著數(shù)十Gbps的流量、上千萬的并發(fā)連接。
什么是負載均衡?
互聯(lián)網(wǎng)早期,業(yè)務流量比較小并且業(yè)務邏輯比較簡單,單臺服務器便可以滿足基本的需求;但隨著互聯(lián)網(wǎng)的發(fā)展,業(yè)務流量越來越大并且業(yè)務邏輯也越來越復雜,單臺機器的性能問題以及單點問題凸顯了出來,因此需要多臺機器來進行性能的水平擴展以及避免單點故障。但是要如何將不同的用戶的流量分發(fā)到不同的服務器上面呢?
早期的方法是使用DNS做負載,通過給客戶端解析不同的IP地址,讓客戶端的流量直接到達各個服務器。但是這種方法有一個很大的缺點就是延時性問題,在做出調(diào)度策略改變以后,由于DNS各級節(jié)點的緩存并不會及時的在客戶端生效,而且DNS負載的調(diào)度策略比較簡單,無法滿足業(yè)務需求,因此就出現(xiàn)了負載均衡。
客戶端的流量首先會到達負載均衡服務器,由負載均衡服務器通過一定的調(diào)度算法將流量分發(fā)到不同的應用服務器上面,同時負載均衡服務器也會對應用服務器做周期性的健康檢查,當發(fā)現(xiàn)故障節(jié)點時便動態(tài)的將節(jié)點從應用服務器集群中剔除,以此來保證應用的高可用。
負載均衡又分為四層負載均衡和七層負載均衡。四層負載均衡工作在OSI模型的傳輸層,主要工作是轉(zhuǎn)發(fā),它在接收到客戶端的流量以后通過修改數(shù)據(jù)包的地址信息將流量轉(zhuǎn)發(fā)到應用服務器。
七層負載均衡工作在OSI模型的應用層,因為它需要解析應用層流量,所以七層負載均衡在接到客戶端的流量以后,還需要一個完整的TCP/IP協(xié)議棧。七層負載均衡會與客戶端建立一條完整的連接并將應用層的請求流量解析出來,再按照調(diào)度算法選擇一個應用服務器,并與應用服務器建立另外一條連接將請求發(fā)送過去,因此七層負載均衡的主要工作就是代理。
既然四層負載均衡做的主要工作是轉(zhuǎn)發(fā),那就存在一個轉(zhuǎn)發(fā)模式的問題,目前主要有四層轉(zhuǎn)發(fā)模式:DR模式、NAT模式、TUNNEL模式、FULLNAT模式。
DR模式也叫作三角傳輸,通過修改數(shù)據(jù)包的目的MAC地址來讓流量經(jīng)過二層轉(zhuǎn)發(fā)到達應用服務器,這樣應用服務器就可以直接將應答發(fā)給應用服務器,性能比較好。由于這種模式需要依賴二層轉(zhuǎn)發(fā),因此它要求負載均衡服務器和應用服務器必須在一個二層可達的環(huán)境內(nèi),并且需要在應用服務器上配置VIP。
NAT模式通過修改數(shù)據(jù)包的目的IP地址,讓流量到達應用服務器,這樣做的好處是數(shù)據(jù)包的目的IP就是應用服務器的IP,因此不需要再在應用服務器上配置VIP了。缺點是由于這種模式修改了目的IP地址,這樣如果應用服務器直接將應答包發(fā)給客戶端的話,其源IP是應用服務器的IP,客戶端就不會正常接收這個應答,因此我們需要讓流量繼續(xù)回到負載均衡,負載均衡將應答包的源IP改回VIP再發(fā)到客戶端,這樣才可以保證正常通信,所以NAT模式要求負載均衡需要以網(wǎng)關(guān)的形式存在于網(wǎng)絡中。
TUNNEL模式的優(yōu)缺點和DR是一樣的,并且TUNNEL模式要求應用服務器必須支持TUNNEL功能。
FULLNAT模式是在NAT模式的基礎上做一次源地址轉(zhuǎn)換(即SNAT),做SNAT的好處是可以讓應答流量經(jīng)過正常的三層路由回到負載均衡上,這樣負載均衡就不需要以網(wǎng)關(guān)的形式存在于網(wǎng)絡中了,對網(wǎng)絡環(huán)境要求比較低,缺點是由于做了SNAT,應用服務器會丟失客戶端的真實IP地址。
下面詳細介紹一下FULLNAT模式。首先負載均衡上需要存在一個localip池,在做SNAT時的源IP就是從localip池中選擇的。當客戶端流量到達負載均衡設備以后,負載均衡會根據(jù)調(diào)度策略在應用服務器池中選擇一個應用服務器,然后將數(shù)據(jù)包的目的IP改為應用服務器的IP。同時從localip池中選擇一個localip將數(shù)據(jù)包的源IP改為localip,這樣應用服務器在應答時,目的IP是localip,而localip是真實存在于負載均衡上的IP地址,因此可以經(jīng)過正常的三層路由到達負載均衡。由于FULLNAT比NAT模式多做了一次SNAT,并且SNAT中有選端口的操作,因此其性能要遜色于NAT模式,但是由于其較強的網(wǎng)絡環(huán)境適應性,我們選擇了FULLNAT作為TSGW的轉(zhuǎn)發(fā)模式。
為什么選擇自研四層負載均衡?
選擇自研四層負載均衡的原因主要有兩個:第一個是考慮到硬件負載均衡成本比較高;第二個,隨著業(yè)務流量越來越大,LVS出現(xiàn)了性能瓶頸以及運維成本的上升問題。
硬件負載均衡成本問題
硬件成本:中低端硬件負載均衡價格在數(shù)十萬,高端的上百萬,價格非常昂貴。當我們需要組成一個高可用集群時,需要數(shù)臺機器,成本異常高。
人力成本:硬件負載均衡功能比較強大,配置比較靈活,這也導致在維護上,我們需要一些經(jīng)過專業(yè)培訓的人員,就增加了人力成本。
時間成本:當使用的過程中遇到bug或者新需求需要廠商提供新版本的時候,我們需要經(jīng)過繁瑣的流程向廠商上報,然后廠商再發(fā)布新版本供我們升級,時間周期非常長,在高速發(fā)展的互聯(lián)網(wǎng)行業(yè),這種周期是無法接受的。
LVS的性能問題
最初使用的是LVS+Nginx組成的負載均衡結(jié)構(gòu),LVS做四層負載均衡,Nginx做七層負載均衡,但是隨著流量的高速增長(幾個月內(nèi)無論新建連接數(shù)還是吞吐量都有三倍的增長),LVS故障頻發(fā),性能上出現(xiàn)瓶頸,因此我們自研了一款高性能、高可靠的四層負載均衡TSGW來替換LVS。
TSGW如何實現(xiàn)高性能
下面通過對比LVS的一些性能瓶頸來介紹TSGW是如何實現(xiàn)高性能的。
中斷問題以及協(xié)議棧路徑性能過長問題
中斷是影響LVS性能最重要的一個因素,假如我們一秒需要處理600萬的數(shù)據(jù)包,每6個數(shù)據(jù)包產(chǎn)生一個硬件中斷的話,那一秒就會產(chǎn)生100萬個硬件中斷,每一次產(chǎn)生硬件中斷都會打斷正在進行密集計算的負載均衡程序,中間產(chǎn)生大量的cache miss,對性能的影響異常大。
同時由于LVS是基于內(nèi)核netfilter開發(fā)的一個應用程序,而netfilter是運行在內(nèi)核協(xié)議棧的一個鉤子框架。這就意味著當數(shù)據(jù)包到達LVS時,已經(jīng)經(jīng)過了一段很長的協(xié)議棧處理,但是這段處理對于LVS來說都不是必需的,這也造成了一部分不必要的性能損耗。
針對這兩個問題,解決方法是使用輪詢模式的驅(qū)動以及做kernel bypass,而DPDK提供的用戶態(tài)PMD驅(qū)動恰好可以解決這兩個問題。DPDK在設計時使用了大量硬件相關(guān)特性比如numa、 memory channel、 DDIO等,對性能優(yōu)化非常大,同時提供了比較多網(wǎng)絡方面的庫,可以大大減小開發(fā)難度,提高開發(fā)效率。因此選擇DPDK作為TSGW的開發(fā)框架。
鎖
由于內(nèi)核是一個比較通用的應用程序,因此它并沒有對一些特定場景做一些定制設計,這就導致一些公共的數(shù)據(jù)結(jié)構(gòu)需要鎖的保護。下面介紹一下出現(xiàn)鎖的原因和TSGW的解決方法。
首先介紹一下RSS(Receive Side Scaling),RSS是一個通過數(shù)據(jù)包的元組信息將數(shù)據(jù)包散列到不同網(wǎng)卡隊列的功能,這時候不同的CPU再去對應的網(wǎng)卡隊列讀取數(shù)據(jù)進行處理,就可以充分利用CPU資源。之前介紹TSGW使用FULLNAT的模式,F(xiàn)ULLNAT會將數(shù)據(jù)包的元組信息全部改變,這樣同一個連接,請求和應答方向的數(shù)據(jù)包有可能會被RSS散列到不同的網(wǎng)卡隊列中,在不同的網(wǎng)卡隊列也就意味著在被不同的CPU進行處理,這時候在訪問session結(jié)構(gòu)的時候就需要對這個結(jié)構(gòu)進行加鎖保護。
解決這個問題的方法有兩種,一種就是在做SNAT選端口的時候,通過選擇一個端口lport0讓RSS(cip0, cport0, vip0, vport0) = RSS(dip0, dport0, lip0, lport0)相等;另外一種方法就是我們?yōu)槊總€CPU分配一個localip,在做SNAT選IP的時候,不同的CPU選擇自己的localip,等應答回來以后,再通過lip和CPU的映射關(guān)系,將指定目的IP的數(shù)據(jù)包送到指定隊列上。
由于第二種方法恰好可以被網(wǎng)卡的flow director特性支持,因此我們選擇了第二種方法來去掉session結(jié)構(gòu)的鎖。
flow director可以根據(jù)一定策略將指定的數(shù)據(jù)包送到指定網(wǎng)卡隊列,其在網(wǎng)卡中的優(yōu)先級要比RSS高,因此我們在做初始化的時候就為每個CPU分配一個localip,比如為cpu0分配lip0,為cpu1分配lip1,為cpu2分配lip2,為cpu3分配lip3。 當一個請求包(cip0, cport0, vip0, vport0)到達負載均衡后,被RSS散列到了隊列0上,這時這個包被cpu0處理。cpu0在對其做fullnat時,選擇cpu0自己的localip lip0,然后將數(shù)據(jù)包(lip0, lport0, dip0, dport0)發(fā)到應用服務器,在應用服務器應答后,應答數(shù)據(jù)包(dip0, dport0, lip0,
lport0)被發(fā)到了負載均衡服務器。此時我們就可以在flow director下一條將目的IP為lip0的數(shù)據(jù)包送到隊列0的規(guī)則,這樣應答數(shù)據(jù)包就會被送到隊列0讓cpu0處理。這時候CPU在對同一個連接兩個方向的數(shù)據(jù)包進行處理的時候就是完全串行的一個操作,也就不要再對session結(jié)構(gòu)進行加鎖保護了。
上下文切換
在設計時,希望控制平面與數(shù)據(jù)平面完全分離,數(shù)據(jù)平面專心做自己的處理,不被任事件打斷。因此將CPU分成兩組,一組用作數(shù)據(jù)平面一組用做控制平面。同時,對數(shù)據(jù)平面的CPU進行CPU隔離,這樣控制平面的進程就不會調(diào)度到數(shù)據(jù)平面的這組CPU上面了;對數(shù)據(jù)平面的線程進行CPU綁定,這樣就可以讓每個數(shù)據(jù)線程獨占一個CPU。 其他的控制平面的程序比如Linux kernel、 SSH等都跑在控制平面的這組CPU上。
TSGW如何做到高可靠
下面從TSGW集群、TSGW單機以及應用服務器層這三個層介紹TSGW如何在每一層實現(xiàn)高可靠。
集群的高可靠
TSGW使用OSPF+ECMP的模式組成集群,通過ECMP將數(shù)據(jù)包散列到集群中各個節(jié)點上,再通過OSPF保證單臺機器故障以后將這臺機器的路由動態(tài)的剔除出去,這樣ecmp就不會再給這臺機器分發(fā)流量,也就做到了動態(tài)的failover。
傳統(tǒng)的ecmp算法有一個很嚴重的問題,當集群中節(jié)點數(shù)量發(fā)生變化以后,會導致大部分流量的路徑發(fā)生改變,發(fā)生改變的流量到達其他TSGW節(jié)點上時是找不到自己的session結(jié)構(gòu)的,這就會導致大量的連接出現(xiàn)異常,對業(yè)務影響很大,并且當我們在對集群做升級操作時會將每個節(jié)點都進行一次下線操作,這樣就加重了這個問題的影響。
一種解決方式是使用支持一致性hash的交換機,這樣在節(jié)點發(fā)生變化的時候,只有發(fā)生變化的節(jié)點上面的連接會有影響,其他連接都會保持正常,但是支持這種算法的交換機比較少,并且也沒有完全實現(xiàn)高可用,因此我們做了集群間的session同步功能。
集群中每個節(jié)點都會全量的將自己的session同步出去,使集群中每個節(jié)點都維護一份全局的session表,因此無論節(jié)點變化以后流量的路徑以任何形式改變,這些流量都可以找到自己的session結(jié)構(gòu),也就是說可以被正常的轉(zhuǎn)發(fā),這樣就可以在集群中節(jié)點數(shù)量發(fā)生變化時保證所有連接正常。
在設計的過程中主要考慮了兩個問題:第一個是故障切換,第二個是故障恢復以及擴容。
故障切換
在故障切換的問題上,我們希望在機器故障以后,交換機可以立刻將流量切到其他機器上,因為流量不切走,意味著到達這臺機器流量會被全部丟掉,產(chǎn)生大量丟包。經(jīng)過調(diào)研測試發(fā)現(xiàn),當交換機側(cè)全部使用物理接口并且服務器側(cè)對接口進行斷電時,交換機會瞬間將流量切換到其他機器上。通過一個100ms發(fā)兩個包的測試(客戶端和服務端各發(fā)一個),這種操作方法是0丟包的。
由于故障切換主要依賴于交換機的感知,當服務器上出現(xiàn)一些異常,交換機感知不到時,交換機就無法進行故障切換操作,因此需要一個健康自檢程序,每半秒進行一次健康自檢,當發(fā)現(xiàn)服務器存在異常時就對服務器執(zhí)行網(wǎng)口斷電操作,從而讓流量立刻切走。
故障切換主要依賴于網(wǎng)口斷電操作并且網(wǎng)卡驅(qū)動是跑在主程序里面的,當主程序掛掉以后,就無法再對網(wǎng)口執(zhí)行斷電操作了,因此為了解決這個問題,主進程會捕獲異常信號,當發(fā)現(xiàn)異常時就對網(wǎng)卡進行斷電操作,在斷電操作結(jié)束以后再繼續(xù)將信號發(fā)給系統(tǒng)進行處理。
經(jīng)過以上設計,TSGW可以做到升級操作0丟包,主程序故障0丟包,其他異常(網(wǎng)線等)會有一個最長500ms的丟包,因為這種異常需要靠自檢程序去檢測,而自檢程序的周期是500ms。
故障恢復與擴容
無論是在進行故障恢復還是擴容操作,都會導致集群節(jié)點數(shù)量發(fā)生變化,這樣也就會導致流量路徑發(fā)生變化。當變化的流量到達集群中原有的節(jié)點時,因為原有節(jié)點都維護著一個全局的session表,因此這些流量是可以被正常轉(zhuǎn)發(fā)的;但是如果流量到達了新機器上,這個機器是沒有全局session表的,那么這部分流量就會全部被丟棄。為了解決這個問題,TSGW在上線以后會經(jīng)歷一個預上線的中間狀態(tài),在這個狀態(tài)上,TSGW不會讓交換機感知到自己上線了,這樣交換機也就不會把流量切過來。首先TSGW會對集群中其他節(jié)點發(fā)送一個批量同步的請求,其他節(jié)點收到請求以后會將自己的session全量的同步到新上線的節(jié)點上,新上線節(jié)點在收到全部session以后才會讓交換機感知到自己上線,這時交換機再將流量切過來就可以正常被轉(zhuǎn)發(fā)出去了。
在這個過程中主要存在兩點問題。
第一個問題是,由于集群中并沒有一個主控節(jié)點來維護一個全局的狀態(tài),如果request報丟失或者session同步的數(shù)據(jù)丟失的話,那新上線節(jié)點就沒辦法維護一個全局的session狀態(tài)。但是考慮到所有節(jié)點都維護著一個全局的session表,因此所有節(jié)點擁有的session數(shù)量都是相同的,那么就可以在所有節(jié)點每次做完批量同步以后發(fā)送一個finish消息,finish消息中帶著自己擁有的session數(shù)量。當新上線節(jié)點收到finish消息以后,便會以自己的session數(shù)量與finish中的數(shù)量做對比。當達到數(shù)量要求以后,新上線節(jié)點就控制自己進行上線操作。否則在等待一定的超時時間以后,重新進行一次批量同步操作,直到達到要求為止。
另外一個問題是在進行批量同步操作時,如果出現(xiàn)了新建連接,那么新建連接就不會通過批量同步同步到新上線的機器上。如果新建連接特別多,就會導致新上線機器一直達不到要求。因此,需要保證處于預上線狀態(tài)的機器能接收到增量同步數(shù)據(jù),因為新建連接可以通過增量同步同步出來。通過增量同步和批量同步就可以保證新上線機器可以最終獲得一個全局的session表。
單機高可靠
在單機高可靠方面,TSGW做了一個自動化測試平臺,自動化平臺通過連通性和配置的正確性來判斷一個測試用例是否執(zhí)行成功,失敗的測試用例平臺可以通過郵件通知測試人員。在每次新功能迭代結(jié)束以后,都會將新功能的測試用例加到自動化平臺里面,這樣在每次上線之前都進行一次自動化測試,可以大大避免改動引發(fā)的問題。
在之前,每次上線之前都需要進行一次手動的回歸測試,回歸測試非常耗時并且很容易遺漏用例,但是為了避免改動引發(fā)新問題又不得不做,有了自動化測試平臺以后,大大提高了回歸測試的效率和可靠性。
RS可靠性
節(jié)點平滑下線
在RS可靠性方面,TSGW提供了節(jié)點平滑下線功能,主要是為了解決當用戶需要對RS進行升級操作時,如果直接將需要升級的RS下線,那這個RS上存在的所有連接都會失敗,影響到業(yè)務。此時如果調(diào)用TSGW的平滑下線功能,TSGW就可以保證此RS已有連接正常工作,但不會往上面調(diào)度新的連接。當所有已有連接結(jié)束以后,TSGW會上報一個結(jié)束的狀態(tài),用戶就可以根據(jù)這個結(jié)束的狀態(tài)對RS進行升級操作,升級后再調(diào)用上線接口讓這個RS器進行正常的服務。如果用戶平臺支持自動化應用部署,那就可以通過接入云平臺使用平滑下線功能,實現(xiàn)完全自動化且對業(yè)務無影響的升級操作。
一致性源IP Hash調(diào)度器
源IP Hash調(diào)度器主要是保證相同的客戶端的連接被調(diào)度到相同應用服務器上,也就是說建立一個客戶端與應用服務器一對一的映射關(guān)系。普通的源IP Hash調(diào)度器在應用服務器發(fā)生變化以后會導致映射關(guān)系發(fā)生改變,會對業(yè)務造成影響。
因此我們開發(fā)了一致性源IP Hash調(diào)度器,保證在應用服務器集群發(fā)生變化時,只有發(fā)生變化的應用服務器與客戶端的映射關(guān)系發(fā)生改變,其他都是不變的。
為了保證流量的均衡,首先在hash環(huán)上分配固定數(shù)量的虛擬節(jié)點,然后將虛擬機節(jié)點均衡的重分布到物理節(jié)點上,重分布算法需要保證兩點:
在物理節(jié)點發(fā)生變化時,只有少數(shù)虛擬節(jié)點映射關(guān)系發(fā)生變化,也就是要保證一致性Hash的基本原則。
因為TSGW是以集群的形式存在的,當多個應用服務器發(fā)生上線下線操作時,反饋到不同的TSGW節(jié)點上就有可能會出現(xiàn)順序不一致的問題,因此無論不同的TSGW節(jié)點產(chǎn)生何種應用服務器上下線順序,都需要保證最終的映射關(guān)系一致,因為如果不一致就導致相同客戶端的連接會被不同的TSGW節(jié)點調(diào)度到不同的應用服務器上,也就違背了源IP Hash調(diào)度器的原則。
綜合以上兩點,Google Maglev負載均衡的一致性Hash算法是一個很好的例子,在paper中有詳細的介紹,這里就不過多討論了。
總結(jié)
經(jīng)過以及騰軟物聯(lián)網(wǎng)云的流量驗證,TSGW無論在傳統(tǒng)網(wǎng)絡環(huán)境還是overlay的大二層環(huán)境下都有出色的性能和穩(wěn)定性表現(xiàn)。在業(yè)務場景方面涵蓋數(shù)據(jù)庫業(yè)務,千萬級別的長連接業(yè)務,嵌入式業(yè)務,存儲業(yè)務以及騰軟商城、APP、數(shù)據(jù)大屏等Web應用業(yè)務。在業(yè)務需求快速變化的環(huán)境下,TSGW在不斷完善自身功能,在各種業(yè)務場景下都有良好的表現(xiàn)。 在未來的一段時間內(nèi),TSGW除了會完善四層的功能需求外,也會考慮向七層方向發(fā)展。