DPVS——愛奇藝的開源四層負載均衡
來自專欄負載均衡
DPVS的整體思路是在DPDK中抽象一個設備層和一個IPVS層,IPVS層完成LVS中完成的功能,設備層用於抽象各種網卡設備。這個設備層是DPVS相比其他方案會多做的事情。如果是一個簡單的負載均衡的實現,是不會做這個設備層的,這個設備層對應於內核中對設備的管理,同時還對應的實現了鄰居表(ARP)和路由表。有了這個設備層,ARP和路由的實現才有了可能。因為設備層提供了一個設備的概念和一個IP的概念。
Ip命令是Linux下網路管理的非常強大和最常用的命令,DPVS在DPDK中完整的實現了對ip命令的支持,所以使用ip命令可以管理網路的方方面面,link,ip,arp,route等。這就對應的要求DPVS中有link,ip,arp,route這些概念。而這一切的前提是網卡設備的抽象。所以可以看到netif模塊是DPVS中最龐大的模塊,而這一整個模塊都是為了完備性而產生的,而不是為了負載均衡這個功能本身而產生的(雖然也有點意義)。
DPVS不是為了滿足企業單一使用環境下的功能設計,而是為了做一個完備的協議棧式的大而全的四層均衡系統,這個系統的繼續發展,將會給行業帶來革命性的改善。但是對於愛奇藝本身,領導者想必要頂著巨大的壓力才能夠推進這些與主線業務不相關的功能設計。DPVS將成為全球範圍內四層負載均衡的標杆軟體,這個毋庸置疑。
DPVS對網卡,IP,ARP和路由表的實現與內核有一些不同,最大的區別在於DPVS是一個DPDK程序,DPDK程序的特點是所有的實現都要是每個核一份。有一些硬體的相關的高性能需求,DPVS也必須要在架構上向硬體讓步。例如在實現IP地址管理的時候,inet_addr是每一個設備可以有多個的,這個模型是符合內核的思想的,但是在選擇lip的時候(就是fnat情況下的本地ip的選擇),DPVS採用的是sa_pool機制,這個機制依賴網卡的Flow Director功能,整體的思想是根據Flow Director的用法來的。Flow Director的功能是通過設置不同的過濾條件,使得不同的流導入到不同的隊列,它的用途在於FNAT的回包上,可以讓從哪個核出去的數據包就回到哪個核。
DPVS非常巧妙的從架構的層面依賴並且利用了這個硬體機制。他將DPVS的LIP的不同埠分成多個不同的組,組的個數取決於數據核的個數。每個LIP都會有一個數據核個數的哈希表,這個哈希表是埠的空間劃分。例如有40個數據核,那麼就會有40個哈希表,每個哈希表代表了埠中的一部分,以最低位作為mask的一個空間劃分。例如第一個哈希表中的埠是1,41,81,121。。。,第二個哈希表中的埠是2,42,82,122。。。,以此類推。從埠的組織上,將埠空間劃分(DPVS默認從1024埠到65535埠進行劃分),然後LIP要選擇哪一個埠的時候,是直接在當前核對應的哈希表中直接選擇一個沒有人用過的埠。
只要這樣簡單的一個操作,回包就可以保證回到這個發送核,這一步的設計是非常的精巧的。因為Flow Director正好提供的是IP和埠和mask三個維度進行的收包隊列的映射。如此一配置,在啟動階段就可以配置好FDIR,而使用的時候僅僅是一個查表,這是DPVS中將硬體機制與軟體架構結合設計的一個典範。
DPVS的網卡設備的抽象程度已經比較高,IP地址的管理上由於是一個負載均衡項目,設備的IP和IPVS的IP兩個概念已經在努力進行區分,但是仍然沒有比較清晰的邊界。按照DPVS的設計思路,應該是設備抽象出來後,設備可以自由的擁有IP,有了這個IP之後,這個IP對應的ARP表,路由表等就可以正常的產生和使用。但是實際中,DPVS沒有忘記自己是一個IPVS程序,在ipvsadm工具下,是可以直接屏蔽DPVS中的網卡的概念的。Ipvsadm設置一個lip是可以同時設置到IPVS子系統和網卡子系統中。也就是說當設置了一個LIP,網卡同時擁有了這個LIP作為自己的地址,同時IPVS子系統有了這個LIP作為FNAT的源IP選擇的範圍。LIP的選擇和網卡設備的IP之間的橋樑是sa_pool,ip_vs_laddr.c是IPVS子系統對LIP的視角,選擇哪一個LIP是由IPVS子系統決定的,但是選擇哪一個LPORT(源埠),就是由剛剛提到的sa_pool和inet_addr之間的FDIR機制來決定的。
而VIP在DPVS中也是一個同時存在於netif和IPVS兩個部分的概念,VIP是負載均衡的概念,但是在DPVS中,他與LIP一樣,也同時對應一個inet_addr(就是netif的IP地址),只是VIP不像LIP是一個ipvsadm命令就同時設置了兩部分,而是需要手工的分別設置。這也反映了架構上的權衡。實際上設置了VIP確實是沒有必要一定要設置netif的IP的,因為按照DPVS的思想,設置了IP的價值在於可路由和可ARP定址。如果使用ECMP模型,交換機的數據包VIP並不依賴VIP本身的定址,而是KNI的IP地址的OSPF的路由宣告,所以VIP本身是不需要ARP的。至於路由,直接強制添加一個路由即可,因為路由並不一定要檢查設備的IP地址。
設備的IP地址與路由表的關係也是參考了Linux內核的實現,在給設備添加了一個IP地址之後,同時會添加兩個路由條目,一個是這個區域網的鏈路路由,也就是到同一個網段的其他IP的訪問,會自動的走ARP協議去查找MAC地址。這個很容易理解,因為當給一個網口添加了一個IP地址的時候,DPVS可以預測同一個網段的其他IP也位於和DPVS網口的同一個二層,既然是同一個二層,那麼使用ARP協議就是直接可達的。所以首先添加的是鏈路層的路由。另外一個是到內部的路由,多個網卡之間,源IP是自己剛配置的IP的時候,需要路由通過。所以scope是host的,代表著在本機內部的路由條目。也就是說,在本機內部這個源IP是可路由的。
ARP表和路由表都是依託於netif做的網卡抽象的層面實現的。而IPVS是另外一個核心的功能,就是LVS所能實現的功能本身。IPVS的核心是流表,這個流表在DPVS中是一個非常龐大的流表條目,它包含了各種各樣的與其他系統之間的關係信息,例如統計,定時器,內存分配器,加速等其他功能。所以這個流表的條目非常的大,為了防止攻擊,DPVS實現了Syn Proxy功能。
流表中,DPVS實現了調度層的一個封裝,這一點的設計非常好,有效的規避了下層不同的調度演算法,在實際的使用中,不同種類的調度演算法一般是很少使用的,實際上一般都會使用一致性哈希。所以實現多種調度演算法和一個封裝,從軟體技術上看,是非常的好的,但是從工程上看,很多也是沒有太大必要的。當然,DPVS有今天的這個地位,不但是因為他大,還是因為他全。全本身就是一個競爭力,是一個滿足各種需求場合的統一解決方案。一招吃遍天下,是很多人很樂意接受的解決方案,也是DPVS能在四層負載均衡市場成功的唯一前提。Gitlab的GLB也是開源的,方案不能說不精巧,但是只提供了一種使用場景,滿足了Gitlab企業的需要,但是在DPVS面前,沒有人會選擇使用GLB。這就是全的力量,Linux內核能夠成功也是因為如此。
IPVS子系統在實現的時候同時考慮支持DR,Tunnel,FNAT,DNAT,SNAT,這本身就是一件非常難做的架構設計。好在有LVS的設計可以供參考。IPVS在實現各個子功能的時候,能夠有效的暴露dpip命令和ipvsadm命令需要的數據,對於架構設計又是一個比較大的考驗,這一切,DPVS雖然不能說已經完備了,但是已經是非常的難得。例如Syn Proxy的邏輯與正常的邏輯不是嚴格的模塊劃分,只是功能上做到了可以停掉Syn Proxy而已。還有很多細節的功能點還有很多值得完善的地方,但是IPVS這個子系統,已經基本具備了替代LVS的能力。
值得一提的是DPVS的配置子系統,採用註冊的方式來約定配置的層級和架構,這在底層的項目中是很少見的。使用了這個配置引擎,可以做到比較複雜的配置解析,但是引擎的實現比較麻煩。但是一旦有了這套引擎,新配置的定義將會是非常的方便。比起json,在規範性上好很多,比起INI,在功能上強大很多。並不是說這樣是最好的,配置沒有最好的方案,很多時候使用多個INI配置文件進行配合反而會更好,使用Nginx式的配置也用的很方便。所以DPVS的配置系統可以說有特色,很強大,但是合理性不能做出評價。
DPVS的一個最大的短板恐怕是在統計數據上,程序執行的情況缺少很多數據進行反應,也缺少數據收集的維度進行數據的分析,更缺少現代的抓包框架。例如sFlow和meter這種分析包的架構和分析數據的架構。更不用說流日誌之類的超大規模數據的產出。
DPVS設計了一個非常有意思的獨立接收核的框架。這個獨立接收核的框架同時工作在專用的接收處理核上,鑒於收包是一個開銷比較大的事情,所以DPVS設計了收包可以由一個單獨的核進行收包,處理由專用的核來處理,當專用的核還有空餘的計算能力的時候,也可以用來收包。這個思想是為重邏輯設計的。因為如果是一般的邏輯強度,收包和處理跑滿整個10G的網卡,CPU也不會滿,即使是25G的網卡,大部分情況下,單核單隊列也是能搞得定的。
DPVS從配置到架構實現了收包和Lcore分配上的靈活性,使得DPVS從一開始就是一個通用的方案,是一個可以承載重度邏輯的方案。當我需要讓CPU承載更多的邏輯的時候,只需要讓多個核同時去負責一個隊列,當我需要精簡的邏輯,例如DR模式,我可以只配置一個核來做處理,但是同時配置多個核用來接收。這種靈活的調度能力是DPVS的配置架構提供的,架構即技術,在DPVS中有一個比較好的體現。但是Lcore和網卡隊列這些關係的映射管理,目前DPVS做的還比較亂,只能說可以用。當然配置多核的時候,需要考慮NUMA,這是配置的時候需要關注的事情。
還有一個比較有意思的子系統是定時器子系統。DPDK原本的定時器子系統就是一個skiplist,skiplist是一個高效的查找鏈表,所以DPDK本身的定時器的性能就不會太差。那DPVS費了非常大的篇幅來實現一個定時器子系統的原因在哪裡?首先,DPVS封裝了獲得精確時間的函數,像Linux應用層的gettimeofday,在DPVS的定時器子系統中被封裝實現了,不是通過直接讀取HPET或者TSC,而是直接從DPVS模塊中由tick計算而來。我們知道HPET的特點是高並發下必然出現性能問題,TSC的特點是不如HPET穩,精度也不夠高,最要命的是,每個核可能還不一樣。但是大部分情況下,TSC的缺點並不構成缺點,很少有如此高精度的需求。DPVS的定時器製造了一個調度器的概念,每個核一個。全局的調度器在master核上。每一個調度器只對應一個DPDK的定時器,這樣整個DPVS就只使用了幾十個DPDK的定時器,這顯然是看不起DPDK定時器的超大規模情況下的性能了。事實上,DPVS是對的,skiplist再強大也是一個list,千萬級的流表使用的定時器,會很快的把這個list拖垮。DPVS的定時器子系統實現了一個「時分秒」的概念,只是不是通常的時分秒的間隔。同個這個時分秒的概念,將定時器在時間空間進行了劃分,每個時間空間的定時器都對應一個哈希表的鏈表。整個單位時間內的圓盤就是一個哈希表,每個點是一個鏈表。如此將時間在空間展開。這個方案的設計可謂精巧絕倫。有了這個定時器子系統,DPVS可以處理幾乎無限大的定時器規模。所以再也不用擔心流表的定時器爆炸的問題了。一般情況下,DPDK的流表都會用專用的老化線程來做,但是由於DPVS的通用設計,使得在數據通道直接進行老化成為可能。
在每個核的任務安排上,DPVS出色的設計了job子系統,每個核要做的周期性的任務都是一個一個的job,job的執行可以設置頻率,整個輪詢邏輯的核心就是不同job的執行,不同的模塊在添加自己的邏輯到主邏輯的時候,都是簡單的使用job介面。這些通用的設計都使得DPVS成為一個標杆式的實現,DPVS的野心,充斥在每一個點的設計上。
安全方面除了Syn Proxy,還做了黑名單和限速,限速部分起名叫TC,可見野心不小。Linux下的tc子系統是非常的強大的,DPVS能否複製這一成功,值得繼續觀察。
另外,KNI,VLAN,跨核的msg消息通信等輔助功能DPVS也有支持。
推薦閱讀:
※手寫mybatis源碼
※谷歌急眼了?今後安卓可能不再免費
※無名科創開源飛控
※用天文曆法解開源頭文化之謎(劉明武)
※碼雲推薦 | 跨平台 Redis 桌面管理工具 Kedis