0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

如何基于最新的BPF/XDP特性來應對這些挑戰(zhàn)

Linux閱碼場 ? 來源:Linux閱碼場 ? 作者:Linux閱碼場 ? 2022-04-26 14:29 ? 次閱讀

標題可直譯為《從 XDP 到 Socket 的(全路徑)流量路由:XDP 不夠,BPF 來湊》,因為 XDP 運行 在網(wǎng)卡上,而且在邊界和流量入口,再往后的路徑(尤其是到了內(nèi)核協(xié)議棧)它就管不 到了,所以引入了其他一些 BPF 技術來“接力”這個路由過程。另外, 這里的“路由”并非狹義的路由器三層路由,而是泛指 L3-L7 流量轉發(fā)。

翻譯時加了一些鏈接和代碼片段,以更方便理解。

1 引言1.1 前期工作1.2 Facebook 流量基礎設施1.3 面臨的挑戰(zhàn) 2 選擇后端主機:數(shù)據(jù)中心內(nèi)流量的一致性與無狀態(tài)路由(四層負載均衡)2.1 Katran (L4LB) 負載均衡機制2.2 一致性哈希的局限性2.2.1 容錯性:后端故障對非相關連接的擾動2.2.2 TCP 長連接面臨的問題2.2.3 QUIC 協(xié)議為什么不受影響 2.3 TCP 連接解決方案:利用 BPF 將 backend server 信息嵌入 TCP Header2.3.1 原理和流程2.3.2 開銷2.3.3 實現(xiàn)細節(jié)2.3.4 效果2.3.5 限制2.4 小結3 選擇 socket:服務的真正優(yōu)雅發(fā)布(七層負載均衡)3.1 當前發(fā)布方式及存在的問題3.1.1 發(fā)布流程3.1.2 存在的問題3.2 不損失容量、快速且用戶無感的發(fā)布3.2.1 早期方案:socket takeover (or zero downtime restart)3.2.2 其他方案調(diào)研:SOREUSEPORT3.2.3 思考3.3 新方案:bpfskreuseport3.3.1 方案設計3.3.2 好處3.3.3 發(fā)布過程中的流量切換詳解3.3.4 新老方案效果對比3.3.5 小結4 討論4.1 遇到的問題:CPU 毛刺(CPU spikes)甚至卡頓4.2 Listening socket hashtable4.3 bpfskselectreuseport vs bpfsk_lookup

1.引言

用戶請求從公網(wǎng)到達 Facebook 的邊界 L4LB 節(jié)點之后,往下會涉及到兩個階段(每個階 段都包括了 L4/L7)的流量轉發(fā):

1.從 LB 節(jié)點負載均衡到特定主機2.主機內(nèi):將流量負載均衡到不同 socket

以上兩個階段都涉及到流量的一致性路由(consistent routing of packets)問題。本文介紹這一過程中面臨的挑戰(zhàn),以及我們?nèi)绾位?a href="http://www.ttokpm.com/article/zt/" target="_blank">最新的 BPF/XDP 特性來應對這些挑戰(zhàn)。

1.1 前期工作

幾年前也是在 LPC 大會,我們分享了 Facebook 基于 XDP 開發(fā)的幾種服務,例如

1.基于 XDP 的四層負載均衡器(L4LB) katran, 從 2017 年開始,每個進入 facebook.com 的包都是經(jīng)過 XDP 處理的;

2.基于 XDP 的防火墻(擋在 katran 前面)。

92440d52-c3bd-11ec-bce3-dac502259ad0.jpg

Facebook 兩代軟件 L4LB 對比。左:第一代,基于 IPVS,L4LB 需獨占節(jié)點;右:第二代,基于 XDP,不需獨占節(jié)點,與業(yè)務后端混布。

1.2 Facebook 流量基礎設施

從層次上來說,如下圖所示,F(xiàn)acebook 的流量基礎設施分為兩層:

1.邊界層(edge tiers),位于 PoP 點2.數(shù)據(jù)中心層,我們稱為 Origin DC

9258e98e-c3bd-11ec-bce3-dac502259ad0.jpg

每層都有一套全功能 LB(L4+L7)

Edge PoP 和 Origin DC 之間的 LB 通常是長鏈接

從功能上來說,如下圖所示:

92708166-c3bd-11ec-bce3-dac502259ad0.jpg

1.用戶連接(user connections)在邊界終結,2.Edge PoP LB 將 L7 流量路由到終端主機,3.Origin DC LB 再將 L7 流量路由到最終的應用,例如 HHVM 服務。

1.3 面臨的挑戰(zhàn)

總結一下前面的內(nèi)容:公網(wǎng)流量到達邊界節(jié)點后,接下來會涉及 兩個階段的流量負載均衡(每個階段都是 L4+L7),

1.宏觀層面:LB 節(jié)點 -> 后端主機2.微觀層面(主機內(nèi)):主機內(nèi)核 -> 主機內(nèi)的不同 socket

這兩個階段都涉及到流量的高效、一致性路由(consistent routing)問題。

本文介紹這一過程中面臨的挑戰(zhàn),以及我們是如何基于最新的 BPF/XDP 特性 來解決這些挑戰(zhàn)的。具體來說,我們用到了兩種類型的 BPF 程序:

1.BPF TCP header options:解決主機外(宏觀)負載均衡問題;2.BPFPROGTYPESKREUSEPORT (及相關 map 類型BPFMAPTYPEREUSEPORTSOCKARRAY):解決主機內(nèi)(微觀)負載均衡問題。

2.選擇后端主機:數(shù)據(jù)中心內(nèi)流量的一致性與無狀態(tài)路由(四層負載均衡)

先看第一部分,從 LB 節(jié)點轉發(fā)到 backend 機器時,如何來選擇主機。這是四層負載均衡問題。

2.1 Katran (L4LB) 負載均衡機制

回到流量基礎設施圖,這里主要關注 Origin DC 內(nèi)部 L4-L7 的負載均衡

928a6e14-c3bd-11ec-bce3-dac502259ad0.jpg

katran 是基于 XDP 實現(xiàn)的四層負載均衡器,它的內(nèi)部機制:

實現(xiàn)了一個 Maglev Hash 變種,通過一致性哈希選擇后端;

在一致性哈希之上,還維護了自己的一個本地緩存來跟蹤連接。這個設計是為了在某些后端維護或故障時,避免其他后端的哈希發(fā)生變化,后面會詳細討論。

用偽代碼來表示 Katran 選擇后端主機的邏輯:


int pick_host(packet* pkt) { if (is_in_local_cache(pkt)) return local_cache[pkt] return consistent_hash(pkt) % server_ring}

這種機制非常有效,也非常高效(highly effective and efficient)。

2.2 一致性哈希的局限性

2.2.1 容錯性:后端故障對非相關連接的擾動

一致性哈希的一個核心特性是具備對后端變化的容錯性(resilience to backend changes)。當一部分后端發(fā)生故障時,其他后端的哈希表項不受影響(因此對應的連接及主機也不受影響)。Maglev 論文中已經(jīng)給出了評估這種容錯性的指標,如下圖,

92a2999e-c3bd-11ec-bce3-dac502259ad0.jpg

Resilience of Maglev hashing to backend changes

Maglev: A fast and reliable software network load balancer. OSDI 2016

橫軸表示 backend 掛掉的百分比

縱軸是哈希表項(entries)變化的百分比,對應受影響連接的百分比

Google 放這張圖是想說明:一部分后端發(fā)生變化時,其他后端受影響的概率非常?。坏珡奈覀兊慕嵌葋碚f,以上這張圖說明:即使后端掛掉的比例非常小, 整個哈希表還是會受影響,并不是完全無感知 —— 這就會 導致一部分流量被錯誤路由(misrouting):

對于短連接來說,例如典型的 HTTP 應用,這個問題可能影響不大;

但對于 tcp 長連接,例如持續(xù)幾個小時的視頻流,這種擾動就不能忍了。

2.2.2 TCP 長連接面臨的問題

首先要說明,高效 != 100% 有效。對于 TCP 長連接來說(例如視頻),有兩種場景會它們被 reset:


int pick_host(packet* pkt) { if (is_in_local_cache(pkt)) // 場景一:ECMP shuffle 時(例如 LB 節(jié)點維護或故障),這里會 miss return local_cache[pkt] return consistent_hash(pkt) % server_ring // 場景二:后端維護或故障時,這里的好像有(較?。└怕拾l(fā)生變化}

解釋一下:

1.如果 LB 升級、維護或發(fā)生故障,會導致路由器 ECMP shuffle,那原來路由到某個 LB 節(jié)點的 flow,可能會被重新路由到另一臺 LB 上;雖然我們維護了 cache,但它是 LB node local 的,因此會發(fā)生 cache miss;

2.如果后端節(jié)點升級、維護或發(fā)生故障,那么根據(jù)前面 maglev 容錯性的實驗結果,會有一 部分(雖然比例不是很大)的 flow 受到影響,導致路由錯誤。

以上分析可以看出,“持續(xù)發(fā)布” L4 和 L7 服務會導致連接不穩(wěn)定,降低整體可靠性。除了發(fā)布之外,我們隨時都有大量服務器要維護,因此哈希 ring 發(fā)生變化(一致性哈希 發(fā)生擾動)是日常而非例外。任何時候發(fā)生 ECMP shuffle 和服務發(fā)布/主機維護,都會導 致一部分 active 連接受損,雖然量很小,但會降低整體的可靠性指標。

解決這個問題的一種方式是在所有 LB 節(jié)點間共享這個 local cache (類似于 L4LB 中的 session replication),但這是個很糟糕的主意 ,因為這就需要去解決另外一大堆分布式系統(tǒng)相關的問題,尤其我們不希望引入任何 會降低這個極快數(shù)據(jù)路徑性能的東西。

2.2.3 QUIC 協(xié)議為什么不受影響

但對于 QUIC 來說,這都不是問題。

connection_id

QUIC 規(guī)范(RFC 9000)中允許 server 將任意信息嵌入到包的 connectionid 字段。

Facebook 已經(jīng)廣泛使用 QUIC 協(xié)議,因此在 Facebook 內(nèi)部,我們可以

1.在 server 端將路由信息(routing information)嵌入到 connection_id 字段

2.要求客戶端必須將這個信息帶回來。

完全無狀態(tài)四層路由這樣整條鏈路上都可以從包中提取這個 id,無需任何哈?;?cache 查找,最終實現(xiàn)的是一個 完全無狀態(tài)的四層路由(completely stateless routing in L4)。

那能不能為 TCP 做類似的事情呢?答案是可以。這就要用到 BPF-TCP header option 了。

2.3 TCP 連接解決方案:利用 BPF 將 backend server 信息嵌入 TCP Header

2.3.1 原理和流程

基本思想:

1.編寫一段 BPFPROGTYPESOCKOPS 類型的 BPF 程序,attach 到 cgroup:在 LISTEN, CONNECT, CONNESTD 等事件時會觸發(fā) BPF 程序的執(zhí)行BPF 程序可以獲取包的 TCP Header,然后往其中寫入路由信息(這里是 serverid),或者從中讀取路由信息2.在 L4LB 側維護一個 server_id 緩存,記錄仍然存活的 backend 主機

以下圖為例,我們來看下 LB 節(jié)點和 backend 故障時,其他 backend 上的原有連接如何做到不受影響:

92b76b12-c3bd-11ec-bce3-dac502259ad0.jpg

1) 客戶端發(fā)起一個 SYN;

2) L4LB 第一次見這條 flow,因此通過一致性哈希為它選擇一臺 backend 主機,然后將包轉發(fā)過去;

3) 服務端應答 SYN+ACK,其中 服務端 BPF 程序將 server_id 嵌入到 TCP 頭中;

圖中這臺主機獲取到自己的 serverid 是 42,然后將這個值寫到 TCP header;

客戶端主機收到包后,會解析這個 id 并存下來,后面發(fā)包時都會帶上這個 serverid;

假設過了一會發(fā)生故障,前面那臺 L4LB 掛了(這會導致 ECMP 發(fā)生變化);另外,某些 backend hosts 也掛了(這會 影響一致性哈希,原有連接接下來有小概率會受到影響),那么接下來

4) 客戶端流量將被(數(shù)據(jù)中心基礎設施)轉發(fā)到另一臺 L4LB;

5) 這臺新的 L4LB 解析客戶端包的 TCP header,提取 serverid,查詢 serverid 緩存( 注意不是 Katran 的 node-local 連接緩存)之后發(fā)現(xiàn) 這臺機器還是 active 的,因此直接轉發(fā)給這臺機器。

可以看到在 TCP Header 中引入了路由信息后,未發(fā)生故障的主機上的長連接就能夠避免 因 L4LB 和主機掛掉而導致的 misrouting(會被直接 reset)。

2.3.2 開銷

數(shù)據(jù)開銷:TCP header 增加 6 個字節(jié)


struct tcp_opt { uint8_t kind; uint8_t len; uint32_t server_id;}; // 6-bytes total

運行時開銷:不明顯需要在 L4LB 中解析 TCP header 中的 serverid 字段,理論上來說,這個開銷跟代碼實 現(xiàn)的好壞相關。我們測量了自己的實現(xiàn),這個開銷非常不明顯。

2.3.3 實現(xiàn)細節(jié)

監(jiān)聽的 socket 事件


switch (skops->op) { case BPF_SOCK_OPS_TCP_LISTEN_CB: case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB: case BPF_SOCK_OPS_TCP_CONNECT_CB: case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB: case BPF_SOCK_OPS_PARSE_HDR_OPT_CB: case BPF_SOCK_OPS_HDR_OPT_LEN_CB: case BPF_SOCK_OPS_WRITE_HDR_OPT_CB: . . .}

維護 TCP flow -> serverid 的映射在每個 LB 節(jié)點上用 bpfskstorage 來存儲 per-flow serverid。也就是說,

1.對于建連包特殊處理,2.建連之后會維護有 flow 信息(例如連接跟蹤),3.對于建連成功后的普通流量,從 flow 信息就能直接映射到 serverid, 不需要針對每個包去解析 TCP header。

serverid 的分配和同步前面還沒有提到如何分配 server_id,以及如何保證這些后端信息在負 載均衡器側的時效性和有效性。

我們有一個 offline 工作流,會給那些有業(yè)務在運行的主機隨機分配 一個 id,然后將這個信息同步給 L4 和 L7 負載均衡器(Katran and Proxygen), 后者拿到這些信息后會將其加載到自己的控制平面。因此這個系統(tǒng)不會有額外開銷,只要 保證 LB 的元信息同步就行了。

由于這個機制同時適用于 QUIC 和 TCP,因此 pipeline 是同一個。

2.3.4 效果

下面是一次發(fā)布,可以看到發(fā)布期間 connection reset 并沒有明顯的升高:

92d12d36-c3bd-11ec-bce3-dac502259ad0.jpg

2.3.5 限制

這種方式要求 TCP 客戶端和服務端都在自己的控制之內(nèi),因此

對典型的數(shù)據(jù)中心內(nèi)部訪問比較有用;

要用于數(shù)據(jù)中心外的 TCP 客戶端,就要讓后者將帶給它們的 server_id 再帶回來,但這個基本做不到;

即使它們帶上了,網(wǎng)絡中間處理節(jié)點(middleboxes)和防火墻(firewalls)也可能會將這些信息丟棄。

2.4 小結

通過將 server_id 嵌入 TCP 頭中,我們實現(xiàn)了一種 stateless routing 機制,

這是一個完全無狀態(tài)的方案

額外開銷(CPU / memory)非常小,基本感知不到

其他競品方案都非常復雜,例如在 hosts 之間共享狀態(tài),或者將 server_id 嵌入到 ECR (Echo Reply) 時間戳字段。

3.選擇socket:服務的真正優(yōu)雅發(fā)布(七層負載均衡)

前面介紹了流量如何從公網(wǎng)經(jīng)過內(nèi)網(wǎng) LB 到達 backend 主機。再來看在主機內(nèi),如何路由流量來保證七層服務(L7 service)發(fā)布或重啟時不損失任何流量。

這部分內(nèi)容在 SIGCOMM 2020 論文中有詳細介紹。想了解細節(jié)的可參考:

Facebook, Zero Downtime Release: Disruption-free Load Balancing of a Multi-Billion User Website. SIGCOMM 2020

3.1 當前發(fā)布方式及存在的問題

L7LB Proxygen 自身也是一個七層服務,我們以它的升級為例來看一下當前發(fā)布流程。

3.1.1 發(fā)布流程

1.發(fā)布前狀態(tài):Proxygen 實例上有一些老連接,也在不斷接受新連接

92e2901c-c3bd-11ec-bce3-dac502259ad0.jpg

2.拉出:拉出之后的實例不再接受新連接,但在一定時間窗口內(nèi),繼續(xù)為老連接提供服務;

這個窗口稱為 graceful shutdown(也叫 draining) period,例如設置為 5 或 10 分鐘;

拉出一般是通過將 downstream service 的健康監(jiān)測置為 false 來實現(xiàn)的,例如在這個例子中,就是讓 Proxygen 返回給 katran 的健康監(jiān)測是失敗的。

92f77130-c3bd-11ec-bce3-dac502259ad0.jpg

3.發(fā)布新代碼:graceful 窗口段過了之后,不管它上面還有沒有老連接,直接開始升級。

部署新代碼

關閉現(xiàn)有進程,創(chuàng)建一個新進程運行新代碼。

930b9bc4-c3bd-11ec-bce3-dac502259ad0.jpg

一般來說,只要 graceful 時間段設置比較合適,一部分甚至全部老連接能夠在這個 窗口內(nèi)正常退出,從而不會引起用戶可見的 spike;但另一方面,如果此時仍然有老 連接,那這些客戶端就會收到 tcp reset。

4.監(jiān)聽并接受新連接:升級之后的 Proxygen 開始正常工作, 最終達到和升級之前同等水平的一個連接狀態(tài)。

93271d54-c3bd-11ec-bce3-dac502259ad0.jpg

3.1.2 存在的問題

很多公司都是用的以上那種發(fā)布方式,它的實現(xiàn)成本比較低,但也存在幾個問題:

1.發(fā)布過程中,系統(tǒng)容量會降低。

從 graceful shutdown 開始,到新代碼已經(jīng)接入了正常量級的流量,這段時間內(nèi) 系統(tǒng)容量并沒有達到系統(tǒng)資源所能支撐的最大值, 例如三個 backend 本來最大能支撐 3N 個連接,那在升級其中一臺的時間段內(nèi),系統(tǒng)能支撐的最大連接數(shù)就會小于 3N,在 2N~3N 之間。這也是為什么很多公司都避免在業(yè)務高峰(而是選擇類似周日凌晨五點這樣的時間點)做這種變更的原因之一。

2.發(fā)布周期太長

假設有 100 臺機器,分成 100 個批次(phase),每次發(fā)布一臺, 如果 graceful time 是 10 分鐘,一次發(fā)布就需要 1000 分鐘,顯然是不可接受的。

本質(zhì)上來說,這種方式擴展性太差,主機或實例數(shù)量一多效率就非常低了。

3.2 不損失容量、快速且用戶無感的發(fā)布

以上分析引出的核心問題是:如何在用戶無感知的前提下,不損失容量(without losing capacity)且非??焖伲╲ery high velocity)地完成發(fā)布。

3.2.1 早期方案:socket takeover (or zero downtime restart)

我們在早期自己實現(xiàn)了一個所謂的 zero downtime restart 或稱 socket takeover 方案。具體細節(jié)見前面提到的 LPC 論文,這里只描述下大概原理:相比于等待老進程的連接完全退出再開始發(fā)布,我們的做法是直接創(chuàng)建一個新進程,然后通過一個唯 一的 local socket 將老進程中 TCP listen socket 和 UDP sockets 的文件描述符 (以及 SCM rights)轉移到新進程。

發(fā)布流程如下圖所示,發(fā)布前,實例正常運行,同時提供 TCP 和 UDP 服務,其中,

TCP socket 分為兩部分:已接受的連接(編號 1~N)和監(jiān)聽新連接的 listening socket

UDP socket,bind 在 VIP 上

933e37aa-c3bd-11ec-bce3-dac502259ad0.png

接下來開始發(fā)布:

1.創(chuàng)建一個新實例2.將 TCP listening socket 和 UDP VIP 遷移到新實例;老實例仍然 serving 現(xiàn)有 TCP 連接(1 ~ N),3.新實例開始接受新連接(N+1 ~ +∞),包括新的 TCP 連接和新的 UDP 連接4.老實例等待 drain

可以看到,這種方式:

1.在發(fā)布期間不會導致系統(tǒng)容器降低,因為我們完全保留了老實例,另外創(chuàng)建了一個新實例2.發(fā)布速度可以顯著加快,因為此時可以并發(fā)發(fā)布多個實例3.老連接被 reset 的概率可以大大降低,只要允許老實例有足夠的 drain 窗口

那么,這種方式有什么缺點嗎?

存在的問題一個顯而易見的缺點是:這種發(fā)布方式需要更多的系統(tǒng)資源,因為對于每個要升級的實例 ,它的新老實例需要并行運行一段時間;而在之前發(fā)布模型是干掉老實例再創(chuàng)建新實例, 不會同時運行。

但我們今天要討論的是另一個問題:UDP 流量的分發(fā)或稱解復用(de-multiplex)。

TCP 的狀態(tài)維護在內(nèi)核。

UDP 協(xié)議 —— 尤其是維護連接狀態(tài)的 UDP 協(xié)議,具體來說就是 QUIC —— 所有 狀態(tài)維護在應用層而非內(nèi)核,因此內(nèi)核完全沒有 QUIC 的上下文。

由于 socket 遷移是在內(nèi)核做的,而內(nèi)核沒有 QUIC 上下文(在應用層維護),因此 當新老進程同時運行時,內(nèi)核無法知道對于一個現(xiàn)有 UDP 連接的包,應該送給哪個進程 (因為對于 QUIC 沒有 listening socket 或 accepted socket 的概念),因此有些包會到老進程,有些到新進程,如下圖左邊所示;

935cb964-c3bd-11ec-bce3-dac502259ad0.png

為解決這個問題,我們引入了用戶空間解決方案。例如在 QUIC 場景下,會查看 ConnectionID 等 QUIC 規(guī)范中允許攜帶的元信息,然后根據(jù)這些信息,通過另一個 local socket 轉發(fā)給相應的老進程,如以上右圖所示。

雖然能解決 QUIC 的問題,但可以看出,這種方式非常復雜和脆弱,涉及到大量進程間通信,需要維護許多狀態(tài)。有沒有簡單的方式呢?

3.2.2 其他方案調(diào)研:SO_REUSEPORT

Socket takeover 方案復雜性和脆弱性的根源在于:為了做到客戶端無感,我們在兩個進程間共享了同一個 socket。因此要解決這個問題,就要避免在多個進程之間共享 socket。

這自然使我們想到了 SO_REUSEPORT: 它允許 多個 socket bind 到同一個 port。但這里仍然有一個問題:UDP 包的路由過程是非一致的(no consistent routing for UDP packets),如下圖所示:

93725b2a-c3bd-11ec-bce3-dac502259ad0.jpg

如果新老實例的 UDP socket bind 到相同端口,那一個實例重啟時,哈希結果就會發(fā)生變化,導致這個端口上的包發(fā)生 misrouting。

另一方面,SO_REUSEPORT 還有性能問題,

TCP 是有一個獨立線程負責接受連接,然后將新連接的文件描述符轉給其他線程 ,這種機制在負載均衡器中非常典型,可以認為是在 socket 層做分發(fā);

UDP 狀態(tài)在應用層,因此內(nèi)核只能在 packet 層做分發(fā), 負責監(jiān)聽 UDP 新連接的單個線性不但要處理新連接,還負責包的分發(fā),顯然會存在瓶頸和擴展性問題。

93863352-c3bd-11ec-bce3-dac502259ad0.jpg

因此直接使用 SO_REUSEPORT 是不行的。

3.2.3 思考

我們后退一步,重新思考一下我們的核心需求是什么。有兩點:

1.在內(nèi)核中實現(xiàn)流量的無損切換,以便客戶端完全無感知;

2.過程能做到快速和可擴展,不存在明顯性能瓶頸;

內(nèi)核提供了很多功能,但并沒有哪個功能是為專門這個場景設計的。因此要徹底解決問題,我們必須引入某種創(chuàng)新。

理論上:只要我們能控制主機內(nèi)包的路由過程(routing of the packets within a host),那以上需求就很容易滿足了。

實現(xiàn)上:仍然基于 SOREUSEPORT 思想,但同時解決 UDP 的一致性路由和瓶頸問題。最終我們引入了一個 socket 層負載均衡器 bpfsk_reuseport。

3.3 新方案:bpfskreuseport

3.3.1 方案設計

簡單來說,

1.在 socket 層 attach 一段 BPF 程序,控制 TCP/UDP 流量的轉發(fā)(負載均衡):2.通過一個 BPF map 維護配置信息,業(yè)務進程 ready 之后自己配置流量切換。

3.3.2 好處

這種設計的好處:

1.通用,能處理多種類型的協(xié)議。

2.在 VIP 層面,能更好地控制新進程(新實例)啟動后的流量接入過程,例如

Proxygen 在啟動時經(jīng)常要做一些初始化操作,啟動后做一些健康檢測工作, 因此在真正開始干活之前還有一段并未 ready 接收請求/流量的窗口 —— 即使它此時已經(jīng) bind 到端口了。

在新方案中,我們無需關心這些,應用層自己會判斷新進程什么時候可以接受流量 并通知 BPF 程序做流量切換;

3.性能方面,也解決了前面提到的 UDP 單線程瓶頸;

4.在包的路由(packet-level routing)方面,還支持根據(jù) CPU 調(diào)整路由權重(adjust weight of traffic per-cpu)。例如在多租戶環(huán)境中,CPU 的利用率可能并不均勻,可以根據(jù)自己的需要實現(xiàn)特定算法來調(diào)度,例如選擇空閑的 CPU。

5.最后,未來迭代非常靈活,能支持多種新場景的實驗,例如讓每個收到包從 CPU 負責處理該包,或者 NUMA 相關的調(diào)度。

3.3.3 發(fā)布過程中的流量切換詳解

用一個 BPFMAPTYPEREUSEPORTSOCKARRAY 類型的 BPF map 來配置轉發(fā)規(guī)則,其中,

key::

value:socket 的文件描述符,與業(yè)務進程一一對應

如下圖所示,即使新進程已經(jīng)起來,但只要還沒 ready(BPF map 中仍然指向老進程)

939d5582-c3bd-11ec-bce3-dac502259ad0.jpg

BPF 就繼續(xù)將所有流量轉給老進程

93ba0a06-c3bd-11ec-bce3-dac502259ad0.jpg

新進程 ready 后,更新 BPF map,告訴 BPF 程序它可以接收流量了

93cf5762-c3bd-11ec-bce3-dac502259ad0.jpg

BPF 程序就開始將流量轉發(fā)給新進程了

93e7570e-c3bd-11ec-bce3-dac502259ad0.jpg

前面沒提的一點是:我們?nèi)匀幌M麑?UDP 包轉發(fā)到老進程上,這里實現(xiàn)起來其實就非常簡單了: 1.已經(jīng)維護了 flow -> socket 映射2.如果 flow 存在,就就轉發(fā)到對應的 socket;不存在在創(chuàng)建一個新映射,轉發(fā)給新實例的 socket。 這也解決了擴展性問題,現(xiàn)在可以并發(fā)接收包(one-thread-per-socket),不用擔心新進程啟動時的 disruptions 或 misrouting 了:

93f943ba-c3bd-11ec-bce3-dac502259ad0.jpg

3.3.4 新老方案效果對比 先來看發(fā)布過程對業(yè)務流量的擾動程度。下圖是我們的生產(chǎn)數(shù)據(jù)中心某次發(fā)布的統(tǒng)計,圖中有兩條線: 1.一條是已發(fā)布的 server 百分比2.另一個條是同一時間的丟包數(shù)量

940db9d0-c3bd-11ec-bce3-dac502259ad0.jpg

可以看到在整個升級期間,丟包數(shù)量沒有明顯變化。 再來看流量分發(fā)性能,分別對 socket takeover 和 bpfskreuseport 兩種方式加壓:

942cad22-c3bd-11ec-bce3-dac502259ad0.jpg

1.控制組/對照組(左邊):3x 流量時開始丟包,2.實驗組(右邊):30x,因此還沒有到分發(fā)瓶頸但 CPU 已經(jīng)用滿了,但即使這樣丟包仍然很少。 3.3.5 小結 本節(jié)介紹了我們的基于 BPFPROGTYPESKREUSEPORT 和 BPFMAPTYPEREUSEPORTSOCKARRAY 實現(xiàn)的新一代發(fā)布技術,它能實現(xiàn)主機內(nèi)新老實例流量的無損切換,優(yōu)點: 1.簡化了運維流程,去掉脆弱和復雜的進程間通信(IPC),減少了故障; 2.效率大幅提升,例如 UDP 性能 10x; 3.可靠性提升,例如避免了 UDP misrouting 問題和 TCP 三次握手時的競爭問題。

4.討論

4.1 遇到的問題:CPU 毛刺(CPU spikes)甚至卡頓 生產(chǎn)環(huán)境遇到過一個嚴重問題:新老進程同時運行期間,觀察到 CPU spike 甚至 host locking;但測試環(huán)境從來沒出現(xiàn)過,而且在實現(xiàn)上我們也沒有特別消耗 CPU 的邏輯。 排查之后發(fā)現(xiàn),這個問題跟 BPF 程序沒關系,直接原因是 1.在同一個 netns 內(nèi)有大量 socket,2.新老實例同時以支持和不支持 bpfskreuseport 的方式 bind 到了同一端口

 bind("[::1]:443"); /* without SO_REUSEPORT. Succeed. */ bind("[::2]:443"); /* with    SO_REUSEPORT. Succeed. */ bind("[::]:443");  /* with    SO_REUSEPORT. Still Succeed */
3.bind() 實現(xiàn)中有一個 spinlock 會遍歷一個 hashtable bucket,這個哈希表只用 dstport 作為 key 去哈希, 如果有大量 http endpoints,由于它們的 dst_port 很可能都是 443 和 80, 因此會導致對應哈希槽上的鏈表特別長,在遍歷時就會導致 CPU 毛刺甚至機器卡住。這一問題下一小節(jié)專門介紹。 這個問題花了很長時間排查,因此有人在類型場景下遇到類似問題,很可能跟這個有關。相關內(nèi)核代碼, 修復見 patch。 4.2 Listening socket hashtable 進一步解釋上一小節(jié)提到的 hashtable 導致的 CPU 毛刺甚至卡頓問題以及 Facebook 的改進。這個問題在 Cloudflare 2016 年的分享 The revenge of the listening sockets 中有詳細介紹。
// include/net/inet_hashtables.h


static inline struct sock *__inet_lookup(struct net *net,                     

struct inet_hashinfo *hashinfo,                     

struct sk_buff *skb, int doff,                     

const __be32 saddr, const __be16 sport,                     

const __be32 daddr, const __be16 dport,                     

const int dif, const int sdif,                     

bool *refcounted){   

 u16 hnum = ntohs(dport);    

struct sock *sk;

    // 查找是否有 ESTABLISHED 狀態(tài)的連接    

sk = __inet_lookup_established(net, hashinfo, saddr, sport, daddr, hnum, dif, sdif);    

if (sk)        

return sk;


    // 查找是否有 LISTENING 狀態(tài)的連接    

return __inet_lookup_listener(net, hashinfo, skb, doff, saddr, sport, daddr, hnum, dif, sdif);}
如以上代碼所示,查找一個包對應的 socket 時, 1.首先會查找是否有 ESTABLISHED 狀態(tài)的 socket,如果沒有2.再確認是否有 LISTENING 狀態(tài)的 socket;這一步會查一下 listen hashtable,

它的 bucket 數(shù)量非常小,內(nèi)核宏定義為 32,此外,

這個哈希表 只根據(jù)目的端口(dstport)來做哈希,因此 IP 不同但 dstport 相同的 socket 都會哈希到同一個 bucket (在 Cloudflare 的場景中,有 16K entry 會命中同一個 bucket,形成一個非常長的鏈表)。

_inetlookup_listener() 老代碼就不看了,直接看 5.10 的新代碼,這已經(jīng)包含了 Facebook 的 BPF 功能:

// net/ipv4/inet_hashtables.c

struct sock *__inet_lookup_listener(struct net *net,                    

struct inet_hashinfo *hashinfo,                    

struct sk_buff *skb, int doff,                    

const __be32 saddr, __be16 sport,                    

const __be32 daddr, const unsigned short hnum,                    

const int dif, const int sdif){    

struct inet_listen_hashbucket *ilb2;    

struct sock *result = NULL;    

unsigned int hash2;

    // 如果這里 attach 了 BPF 程序,直接讓 BPF 程序來選擇 socket    

/* Lookup redirect from BPF */    

if (static_branch_unlikely(&bpf_sk_lookup_enabled)) {        

result = inet_lookup_run_bpf(net, hashinfo, skb, doff, saddr, sport, daddr, hnum);        

if (result)            

goto done;    

}
    // 沒有 attach BPF 程序或 BPF 程序沒找到 socket:fallback 到常規(guī)的內(nèi)核查找 socket 邏輯


    hash2 = ipv4_portaddr_hash(net, daddr, hnum);    

ilb2 = inet_lhash2_bucket(hashinfo, hash2);


    result = inet_lhash2_lookup(net, ilb2, skb, doff, saddr, sport, daddr, hnum, dif, sdif);    

if (result)        

goto done;


    /* Lookup lhash2 with INADDR_ANY */    

hash2 = ipv4_portaddr_hash(net, htonl(INADDR_ANY), hnum);    

ilb2 = inet_lhash2_bucket(hashinfo, hash2);


    result = inet_lhash2_lookup(net, ilb2, skb, doff, saddr, sport, htonl(INADDR_ANY), hnum, dif, sdif);done:    

if (IS_ERR(result))        

return NULL;    

return result;}
4.3 bpfskselectreuseport vs bpfsklookup 這種兩種類型的 BPF 程序,分別是 Facebook 和 Cloudflare (根據(jù)各自需求)引入內(nèi)核的, 功能有些相似,因此拿來對比一下。 先看一段 Cloudflare 引入 bpfsklookup 時的 commit message
This series proposes a new BPF program type named BPF_PROG_TYPE_SK_LOOKUP,or BPF sk_lookup for short.


BPF sk_lookup program runs when transport layer is looking up a listeningsocket for a new connection request (TCP), or when looking up anunconnected socket for a packet (UDP).


This serves as a mechanism to overcome the limits of what bind() API allowsto express. Two use-cases driving this work are:


(1) steer packets destined to an IP range, fixed port to a single socket


192.0.2.0/24, port 80 -> NGINX socket


(2) steer packets destined to an IP address, any port to a single socket


198.51.100.1, any port -> L7 proxy socket
更多信息,可參考他們的論文: The ties that un-bind: decoupling IP from web services and sockets for robust addressing agility at CDN-scale, SIGCOMM 2021 可以看到,它也允許多個 socket bind 到同一個 port,因此與 bpfskselect_reuseport 功能有些重疊,因為二者都源于這樣一種限制:在收包時,缺少從應用層直接命令內(nèi)核選擇哪個 socket 的控制能力。 但二者也是有區(qū)別的:

skselectreuseport 與 IP 地址所屬的 socket family 是緊耦合

sk_lookup 則將 IP 與 socket 解耦 —— lets it pick any / netns

審核編輯 :李倩

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 數(shù)據(jù)中心

    關注

    16

    文章

    4516

    瀏覽量

    71618
  • Facebook
    +關注

    關注

    3

    文章

    1429

    瀏覽量

    54475

原文標題:[譯] Facebook 流量路由最佳實踐:從公網(wǎng)入口到內(nèi)網(wǎng)業(yè)務的全路徑 XDP/BPF 基礎設施

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    施耐德電氣全方位配電服務解決方案助力輕松應對各種挑戰(zhàn)

    當上述問題出現(xiàn)時,你是否還在苦思解決之法?2024施耐德電氣配電服務持續(xù)升級中!全能施管家出手,助力輕松應對各種挑戰(zhàn)!
    的頭像 發(fā)表于 08-30 14:25 ?278次閱讀

    使用SiC技術應對能源基礎設施的挑戰(zhàn)

    本文簡要回顧了與經(jīng)典的硅 (Si) 方案相比,SiC技術是如何提高效率和可靠性并降低成本的。然后在介紹 onsemi 的幾個實際案例之前,先探討了 SiC 的封裝和系統(tǒng)集成選項,并展示了設計人員該如何最好地應用它們優(yōu)化 SiC 功率 MOSFET 和柵極驅動器性能,以應對
    的頭像 發(fā)表于 07-25 09:36 ?240次閱讀
    使用SiC技術<b class='flag-5'>應對</b>能源基礎設施的<b class='flag-5'>挑戰(zhàn)</b>

    自動化生產(chǎn)助力溫達電子應對原材料漲價挑戰(zhàn)

    原材料價格均出現(xiàn)全面上漲,甚至有專家預測:銅價未來兩年將會有75%的漲幅。 大環(huán)境的變化,也給連接器的生產(chǎn)和制造帶來了沖擊和挑戰(zhàn),市面上不少連接器廠商都不得不選擇漲價應對。 近期,《國際線纜與連接》記者聯(lián)系到浙江溫達電
    的頭像 發(fā)表于 04-26 10:33 ?210次閱讀

    EMI電磁干擾:挑戰(zhàn)與機遇并存,如何應對是關鍵

    深圳比創(chuàng)達EMC|EMI電磁干擾:挑戰(zhàn)與機遇并存,如何應對是關鍵
    的頭像 發(fā)表于 04-11 10:24 ?385次閱讀
    EMI電磁干擾:<b class='flag-5'>挑戰(zhàn)</b>與機遇并存,如何<b class='flag-5'>應對</b>是關鍵

    Arm支持ISO/SAE 21434標準,應對汽車產(chǎn)品安全新挑戰(zhàn)

    專為汽車領域設計的 Arm 汽車增強 (Automotive Enhanced, AE) IP 旨在應對汽車行業(yè)所面臨的艱巨計算挑戰(zhàn)。而這就不得不提到“網(wǎng)絡安全”這個關鍵話題。
    的頭像 發(fā)表于 03-14 12:20 ?1050次閱讀

    凌感英飛凌XDP7系列熱插拔控制器介紹

    該系列主要有兩款產(chǎn)品,XDP700主要用于電信基礎設施,XDP710則更適合服務器、工業(yè)和數(shù)據(jù)中心電源系統(tǒng)等應用中,以實現(xiàn)安全的熱插拔操作和系統(tǒng)保護。
    的頭像 發(fā)表于 02-20 15:23 ?560次閱讀

    SOLIDWORKS 2024 應對工業(yè)設備設計的獨特挑戰(zhàn)

    在工業(yè)設備設計中,由于其復雜性和特殊性,設計師經(jīng)常面臨一系列獨特的挑戰(zhàn)。SOLIDWORKS 2024作為一款強大的三維CAD軟件,為設計師提供了一系列工具和功能,以應對這些挑戰(zhàn)
    的頭像 發(fā)表于 01-02 14:08 ?319次閱讀

    應對傳統(tǒng)摩爾定律微縮挑戰(zhàn)需要芯片布線和集成的新方法

    應對傳統(tǒng)摩爾定律微縮挑戰(zhàn)需要芯片布線和集成的新方法
    的頭像 發(fā)表于 12-05 15:32 ?459次閱讀
    <b class='flag-5'>應對</b>傳統(tǒng)摩爾定律微縮<b class='flag-5'>挑戰(zhàn)</b>需要芯片布線和集成的新方法

    如何應對不間斷電源(UPS)設計挑戰(zhàn)

    如何應對不間斷電源(UPS)設計挑戰(zhàn)
    的頭像 發(fā)表于 12-04 10:14 ?441次閱讀
     如何<b class='flag-5'>應對</b>不間斷電源(UPS)設計<b class='flag-5'>挑戰(zhàn)</b>

    英飛凌EiceDRIVER?技術以及應對SiC MOSFET驅動的挑戰(zhàn)

    英飛凌工業(yè)功率技術大會上,發(fā)表了《英飛凌EiceDRIVER技術以及應對SiCMOSFET驅動的挑戰(zhàn)》的演講,詳細剖析了SiCMOSFET對驅動芯片的需求,以及我們
    的頭像 發(fā)表于 12-01 08:14 ?821次閱讀
    英飛凌EiceDRIVER?技術以及<b class='flag-5'>應對</b>SiC MOSFET驅動的<b class='flag-5'>挑戰(zhàn)</b>

    ECG子系統(tǒng)設計主要挑戰(zhàn)應對方案

    電子發(fā)燒友網(wǎng)站提供《ECG子系統(tǒng)設計主要挑戰(zhàn)應對方案.pdf》資料免費下載
    發(fā)表于 11-23 10:43 ?0次下載
    ECG子系統(tǒng)設計主要<b class='flag-5'>挑戰(zhàn)</b>及<b class='flag-5'>應對</b>方案

    善用可靠且性價比高的隔離技術應對高電壓設計挑戰(zhàn)

    電子發(fā)燒友網(wǎng)站提供《善用可靠且性價比高的隔離技術應對高電壓設計挑戰(zhàn)》資料免費下載
    發(fā)表于 11-22 09:38 ?0次下載
    善用可靠且性價比高的隔離技術<b class='flag-5'>來</b><b class='flag-5'>應對</b>高電壓設計<b class='flag-5'>挑戰(zhàn)</b>

    內(nèi)核觀測技術BPF詳解

    BPF簡介 BPF,全稱是Berkeley Packet Filter(伯克利數(shù)據(jù)包過濾器)的縮寫。其誕生于1992年,最初的目的是提升網(wǎng)絡包過濾工具的性能。后面,隨著這個工具重新實現(xiàn)BPF的內(nèi)核
    的頭像 發(fā)表于 11-10 10:34 ?1036次閱讀

    高性能網(wǎng)絡框架之XDP技術解析

    還沒有較好的通用用戶態(tài)協(xié)議棧開源項目出現(xiàn)。在這種情況下,XDP借助于eBPF虛擬機技術在網(wǎng)卡驅動層實現(xiàn)高性能網(wǎng)絡框架,且其原生運行在內(nèi)核態(tài)可直通內(nèi)核TCP/UDP協(xié)議棧。XDP作為一種數(shù)據(jù)面高性能框架技術為平衡高速數(shù)據(jù)處理和協(xié)議棧兼容開辟了一個新的道路。
    的頭像 發(fā)表于 11-05 11:19 ?3241次閱讀
    高性能網(wǎng)絡框架之<b class='flag-5'>XDP</b>技術解析

    防爆電器采取四大措施積極應對挑戰(zhàn)

    電子發(fā)燒友網(wǎng)站提供《防爆電器采取四大措施積極應對挑戰(zhàn).pdf》資料免費下載
    發(fā)表于 11-03 09:13 ?1次下載
    防爆電器采取四大措施積極<b class='flag-5'>應對</b><b class='flag-5'>挑戰(zhàn)</b>