B站邊緣網(wǎng)絡四層負載均衡器的探索與應用
01 背景介紹
B站的 CDN 下行邊緣節(jié)點過去是非集群化架構(gòu)。這種架構(gòu)下有幾個弊端:
- 增加調(diào)度邏輯復雜性;
- 同機房流量/負載難以均衡;
- 暴露過多的公網(wǎng)IP,增加安全隱患 (盜鏈等);
- 灰度流量比例分配粒度大;
針對以上問題,我們調(diào)研了常見的四層負載均衡器, 傳統(tǒng)的 SLB,LVS,DPVS 這類四層負載均衡器,在功能上也能滿足我們現(xiàn)有的需求。但是以上幾個負載均衡器均需要獨占機器,進而造成成本升高,資源浪費。
有沒有一種既不增加成本,又能解決邊緣節(jié)點四層負載需求的方案呢?由 Cloudflare 提出的基于 Express Data Path (XDP) 的高性能四層負載均衡器 Unimog[1]性能優(yōu)異,并且可以和后端服務同機部署,在性能上也完全滿足我們邊緣場景的要求。所以我們參考 Cloudflare Unimog 的思想,在其基礎上自研了適用于B站的邊緣四層負載均衡器 Nickel (以下簡稱 Ni) 。
- 與業(yè)務服務同機部署,更劃算;
- 只保留業(yè)務需要功能,更輕量;
- 可針對業(yè)務特點優(yōu)化,更靈活;
目前已部署在自建動態(tài)加速,及自建點直播 CDN 集群化生產(chǎn)環(huán)境中。其支持與后端服務同機部署,底層使用 XDP、Traffic Control (TC) 進行包粒度轉(zhuǎn)發(fā),支持 Direct Server Return (DSR) 模式,支持根據(jù) CPU/QPS (或其他業(yè)務維度) 動態(tài)調(diào)整流量分配。
下面左圖為傳統(tǒng) DSR 模式,右圖為自研負載均衡器 Ni 的 DSR 模式,不需獨占資源,支持與服務同機部署,更符合邊緣場景。
|
02 架構(gòu)設計
2.1 總體設計
四層負載均衡器 Ni 由兩部分組成,控制面和數(shù)據(jù)面??刂泼嬷饕撠煼瞻l(fā)現(xiàn)、配置管理、數(shù)據(jù)上報,及LB規(guī)則的動態(tài)維護等。數(shù)據(jù)面主要由 LoadBalance (XDP) , Redirect (TC Traffic Control) 等模塊組成,主要用來負責數(shù)據(jù)包的轉(zhuǎn)發(fā)。控制面和數(shù)據(jù)面根據(jù)預定義的接口傳輸數(shù)據(jù)。
圖片
開始介紹之前先明確幾個下文中用的到名詞及其意義:
- VIP (virtual IP) :用于統(tǒng)一接受用戶請求,代表當前集群流量入口,下文中VIP指LB所在機器的IP(目前邊緣沒有支持真正虛擬IP的建設)。
- DIP (direct IP) :業(yè)務服務所在機器的 IP。
2.2 控制面
控制面基于開源框架 kglb[2] 結(jié)合邊緣網(wǎng)絡特點做的改造和開發(fā),其核心為生成和維護供數(shù)據(jù)面使用的轉(zhuǎn)發(fā)表。為了保證轉(zhuǎn)發(fā)表的數(shù)據(jù)的正確性、實時性、高效性,控制面使用以下幾個功能和模塊更新信息:
- 服務發(fā)現(xiàn)、管理
Ni 控制面需要維護同集群邊緣節(jié)點的所有服務器信息 (VIP,DIP,Hostname,運營商,權(quán)重等),以及需要感知當前邊緣機房內(nèi)機器或者服務的狀態(tài)變化,如標記為下線的機器不再接受新的連接請求,但是需要維護當前已經(jīng)建立的連接直至其主動斷開;
- 健康檢查
處于異常狀態(tài)的服務在確認其不可用后應該盡快從轉(zhuǎn)發(fā)表中刪除,避免影響范圍擴大。因此機房內(nèi)需要有服務器級別的健康狀態(tài)檢查。目前 Ni 提供多種協(xié)議類型的健康檢查方式,如 Http、Tcp 等。以下為 Http 健康檢查的相關配置字段:
{
"checker": {
"http": {
"scheme": "http",
"uri": "/",
"check_port": 9080,
"codes": [
200
]
}
},
"fall_count": 2,
"interval_ms": 2000,
"rise_count": 2
}
- 支持基于機器使用率/QPS等做負載均衡
Ni定期收集機房內(nèi)各服務器的資源使用情況,以便于根據(jù)資源使用率做動態(tài)調(diào)整。使用收集的信息計算機房內(nèi)機器負載,讓負載偏低的服務分配更多的連接,偏高則反之,從而保證一個邊緣機房內(nèi)所有服務器的負載收斂。QPS類似。
- 基于轉(zhuǎn)發(fā)表Beamer[3]的負載均衡
第一步首先需要一個穩(wěn)定高效的 Hash 計算方法,輸入四元組 (源 IP、源 port、目的 IP、目的 port) 后得到對應的 Hash 索引值,第二步使用索引值轉(zhuǎn)換為 DIP。
圖片
支持按照配置的 DIP 權(quán)重做負載均衡,也可以動態(tài)根據(jù) DIP 所在機器的CPU進行實時的權(quán)重調(diào)整,也就是調(diào)整 Hash 值在整個轉(zhuǎn)發(fā)表中的比例。
圖片
如圖所示,比例變化后 Hash 值對應的DIP也會改變。原本應該發(fā)往 DIP A 的數(shù)據(jù)包,發(fā)給了 DIP B。如果這和數(shù)據(jù)包是發(fā)起建連的 (TCP SYN) ,則B服務器與該數(shù)據(jù)包的 Client 端三次握手建立新連接。但如果數(shù)據(jù)包屬于之前與 A 服務器建立連接的 Client,因為 B 服務器沒有對應的 TCP socket,會向 Client 發(fā)送 RST 斷開連接。
要解決這個問題,需要 B 服務器收到的數(shù)據(jù)包不屬于自己的 socket 后,將這類數(shù)據(jù)包二次轉(zhuǎn)發(fā)給A服務器。也就是說 B 服務器需要知道二次轉(zhuǎn)發(fā)的數(shù)據(jù)包應該發(fā)給誰,如果把這個信息存下來,A 服務器與 Client 的連接就可以繼續(xù)保持。
為了實現(xiàn)這一點,我們擴展了轉(zhuǎn)發(fā)表。使用四元組 Hash 之后的值,對應兩個 DIP,動態(tài)更新后的稱為第一跳,動態(tài)更新前的稱為第二跳。
圖片
如果第一跳和第二跳的DIP是一樣的,即使在判斷后發(fā)現(xiàn)數(shù)據(jù)包不屬于第一跳服務器,也不需要做第二跳的判斷和轉(zhuǎn)發(fā),因此實際我們只需要保留發(fā)生變化的部分。
圖片
關于二次轉(zhuǎn)發(fā)的邏輯,主要分為以下三個部分:
- 如果數(shù)據(jù)包是 SYN,則在第一跳服務器上新建一個連接,保證新連接的數(shù)據(jù)包都在第一跳服務器上處理。
- 非SYN的數(shù)據(jù)包,需要檢查第一跳服務器上是否存在相應的 socket。如果存在,則交由第一跳服務器處理。
- 第一跳不存在對應的 socket,則將該數(shù)據(jù)包轉(zhuǎn)發(fā)給第二跳服務器。如果發(fā)現(xiàn)第二跳為空則將該數(shù)據(jù)包丟棄。
如果出現(xiàn)需要轉(zhuǎn)發(fā)到第二跳的情況,因為多轉(zhuǎn)發(fā)了一次數(shù)據(jù)包,所以在一定程度上會造成帶寬的增大。但是隨著新連接的建立,老連接的斷開,需要二次轉(zhuǎn)發(fā)的數(shù)據(jù)包比例會很快降低。
圖片
同時從理論上來說,只要某個 Client 的連接時間足夠長,經(jīng)過多次轉(zhuǎn)發(fā)表動態(tài)調(diào)整,比如第一跳和第二跳都不是A服務器,那么這個 Client 會因為收到 RST 而斷開?;诋斍暗恼{(diào)整策略,這種情況是不可避免的。對此我們在調(diào)整頻率和調(diào)整策略上都做了以下優(yōu)化:
- 控制最快調(diào)整時間間隔;
- 優(yōu)先選擇通過第一跳和第二跳對換即可實現(xiàn)調(diào)整目標的桶;
- 優(yōu)先選擇最近調(diào)整次數(shù)最少的桶;
- 避免表項大規(guī)模調(diào)整,小步迭代;
- 盡可能保證表項連續(xù)性,減少碎片等;
2.3 數(shù)據(jù)面
控制面維護的轉(zhuǎn)發(fā)表是來指導底層做數(shù)據(jù)轉(zhuǎn)發(fā)的,我們的數(shù)據(jù)轉(zhuǎn)發(fā)模塊使用 XDP 來實現(xiàn),XDP之前在 QUIC 使用其做性能收發(fā)包優(yōu)化[4]時也有介紹,它是 Linux 內(nèi)核網(wǎng)絡棧的最底層集成的數(shù)據(jù)包處理器。當網(wǎng)絡包到達內(nèi)核時,XDP 程序會在早期被執(zhí)行 ,跳過了內(nèi)核協(xié)議棧,提高了包處理的效率,XDP 共有3種模式:
- Offload:XDP 的 eBPF 程序直接 hook 到可編程網(wǎng)卡硬件設備上,而不是在主機 CPU 上執(zhí)行。因為該模式將執(zhí)行從 CPU 上移出,并且處于數(shù)據(jù)鏈路的最前端,過濾效率與性能最高。
- Native:XDP的 eBPF 程序在網(wǎng)絡驅(qū)動程序的早期接收路徑之外直接運行。
- Generic:可以在沒有硬件或驅(qū)動程序支持的主機上執(zhí)行上執(zhí)行 XDP 的 eBPF 程序。缺點:仿真執(zhí)行,需要分配額外的套接字緩沖區(qū),導致性能下降。
Offload 模式雖性能最優(yōu)但需要特定硬件的支持,Native 模式為最常用的模式,掛載在驅(qū)動路徑上,需要驅(qū)動的支持,Generic 模式是內(nèi)核模擬出的一種模式,不依賴于網(wǎng)卡驅(qū)動,不過掛載點靠后,性能在三種模式種最差。綜合邊緣節(jié)點機器網(wǎng)卡、系統(tǒng)等因素,我們在生產(chǎn)環(huán)境選用Native 模式。
圖片
控制面維護的轉(zhuǎn)發(fā)表,傳遞到XDP模塊時,其本身是一個類型為 map in map 的 eBPF map。外側(cè)的 map key 為用戶訪問的服務二元組,即服務的 IP 和 Port,外側(cè)的 map value 對應內(nèi)側(cè)的 eBPF map對象;內(nèi)側(cè)的 map key 為桶的編號,即 Hash 的索引值,value 為一個 simple C struct, 內(nèi)部存儲了第一跳和第二跳的IP地址。數(shù)據(jù)面在進行相應的 hash value calculation 之后,找到一對 Hop IPs,將用戶的原始數(shù)據(jù)包封入由該 Hop IPs 組成的 GUE header 中。
GUE header[5] 為 Github LB 使用的一個私有頭格式,詳細見下圖。在封包完成后,XDP 將該數(shù)據(jù)包傳輸給對應機器,在該機器上,由 TC 通過四元組判斷該連接是否已存在;如果存在,則將該數(shù)據(jù)包解封并傳輸給上層;如果不存在,則根據(jù) GUE header 轉(zhuǎn)發(fā)給下一跳。
圖片
在 TC 解封完成后,如果此邊緣集群支持 VIP,并且服務監(jiān)聽了該 VIP,其已經(jīng)可以正常通過 Linux 網(wǎng)絡協(xié)議棧的 Socket 拿到數(shù)據(jù)包,由于目前我司邊緣集群尚不支持 VIP,除非通過 ip_transparent 等特殊手段,否則后端服務器上的服務無法監(jiān)聽作為 LB 的機器的 IP。為了使后端服務無感,我們選擇使用過渡手段的 netfilter conntrack 對 ingress 數(shù)據(jù)包進行 SNAT (Source Network Address Translation) ,并對 egress 數(shù)據(jù)包進行 DNAT (Destination NAT) 。由于 conntrack 本身的性能瓶頸,會限制 Ni 作為 LB 的能力。不過在當前線上的業(yè)務場景下,并不會達到 conntrack 的性能瓶頸,邊緣節(jié)點支持 VIP 也在和相關部門推進中,未來支持后,去掉 NAT 轉(zhuǎn)換 Ni 的性能會進一步提高。
圖片
03 應用場景
3.1 動態(tài)加速的應用
動態(tài)加速是在傳統(tǒng) CDN 基礎上實現(xiàn)的對數(shù)據(jù)網(wǎng)絡加速進一步優(yōu)化的智能管理服務,通過全方位的 CDN 質(zhì)量監(jiān)控,以及智能易用的節(jié)點調(diào)度等功能,提供穩(wěn)定快速的網(wǎng)絡訪問服務。
動態(tài)加速的節(jié)點分布在全國的各個地區(qū),每個節(jié)點都由多臺機器組成。如果將節(jié)點內(nèi)所有機器全都對外暴露,可能會有以下問題:
- 增加動態(tài)智能選路服務的運算量,一定程度上增加排查問題的復雜度;
- 如果單臺機器不可用時需要通過遠端探測發(fā)現(xiàn)并反饋到選路服務,進而計算新的路并下發(fā),流程較長,造成路徑切換變慢;
- 動態(tài)加速機房多為過保機器,存在硬件配置殘次不齊的情況,性能好的機器應該處理更多的業(yè)務請求;
部署Ni之后
- 將機房內(nèi)機器組成一個集群,收斂流量入口;
- 通過主動健康檢查實時將不可用服務摘流,集群外部無感,服務恢復后自動加回;
- 檢查集群內(nèi)機器的 CPU 使用情況,并根據(jù)配置參數(shù)做出實時調(diào)整;
圖片
下圖為紅色部分代表機器 CPU 升高超出閾值后,自動將該機器接流占比減小的監(jiān)控。
圖片
3.2 點直播CDN集群化場景的應用
單個點直播集群內(nèi)可能有幾臺或幾十臺機器,調(diào)度服務從感知資源池內(nèi)機器狀態(tài)變化到對用戶的請求做出反應,可能會有分鐘級的延遲,存在一定的滯后性。且在多機房、多機器的調(diào)度場景下,基于調(diào)度服務的負載均衡也難以完全將流量打均。將負載均衡能力下沉到機房內(nèi)之后,反應時間可以降低到秒級,靈敏度更高。同時流量調(diào)度的的控制粒度也可以做到更加精細,更有利于提升邊緣集群的利用率。
圖片
下圖為某機房部分機器部署Ni前后48小時的的 QPS 對比,可以看到部署之后可以將請求平均分配到各機器,進而平衡 CPU 使用率。
圖片
圖片
說明:因業(yè)務特性未開啟根據(jù)負載動態(tài)調(diào)整功能。在均衡請求量相同的情況下,因視頻資源不同等因素,CPU 會存在一定的差異。
04 未來展望
目前 Ni 已在動態(tài)加速大節(jié)點及點直播 CDN 集群化場景全量。穩(wěn)定運行保障S13 直播賽事。不過仍有需要補齊和優(yōu)化的地方。
- 支持黑白名單,通過 XDP 過濾邊緣的攻擊;
- 支持 RFC QUIC-LB 定義的規(guī)則;
- 支持基于 VIP 的 DSR 模式,消除因Conntrack造成的限制,進一步降低負載;
參考鏈接:
[1] https://blog.cloudflare.com/unimog-cloudflares-edge-load-balancer/
[2] https://github.com/dropbox/kglb
[3] https://www.usenix.org/system/files/conference/nsdi18/nsdi18-olteanu.pdf
[4] https://mp.weixin.qq.com/s/uPHVo-4rGZNvPXLKHPq9QQ
[5] https://github.com/github/glb-director/blob/master/docs/development/gue-header.md
李宇星
本期作者
李宇星 嗶哩嗶哩資深開發(fā)工程師
馮學銘 嗶哩嗶哩高級開發(fā)工程師
劉宏強嗶哩嗶哩資深開發(fā)工程師