日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

eBPF在大厂的应用

發布時間:2024/4/11 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 eBPF在大厂的应用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文翻譯自 Facebook 在 LPC 2021 大會上的一篇分享:From XDP to Socket: Routing of packets beyond XDP with BPF

因為 XDP 運行在網卡上,而且在邊界和流量入口,再往后的路徑(尤其是到了內核協議棧)它就管不到了,所以引入了其他一些BPF技術來“接力”這個路由過程。另外, 這里的“路由”并非狹義的路由器三層路由,而是泛指 L3-L7 流量轉發。

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

由于譯者水平有限,本文不免存在遺漏或錯誤之處。如有疑問,請查閱原文。

以下是譯文。


  • 譯者序

  • 1 引言

    • 1.1 前期工作

    • 1.2 Facebook 流量基礎設施

    • 1.3 面臨的挑戰

  • 2 選擇后端主機:數據中心內流量的一致性與無狀態路由(四層負載均衡)

    • 2.3.1 原理和流程

    • 2.3.2 開銷

    • 2.3.3 實現細節

    • 2.3.4 效果

    • 2.3.5 限制

    • 數據開銷:TCP header 增加 6 個字節

    • 運行時開銷:不明顯

    • 監聽的 socket 事件

    • 維護 TCP flow -> server_id 的映射

    • server_id 的分配和同步

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

    • 2.2.2 TCP 長連接面臨的問題

    • 2.2.3 QUIC 協議為什么不受影響

    • connection_id

    • 完全無狀態四層路由

    • 2.1 Katran (L4LB) 負載均衡機制

    • 2.2 一致性哈希的局限性

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

    • 2.4 小結

  • 3 選擇 socket:服務的真正優雅發布(七層負載均衡)

    • 3.3.1 方案設計

    • 3.3.2 好處

    • 3.3.3 發布過程中的流量切換詳解

    • 3.3.4 新老方案效果對比

    • 3.3.5 小結

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

    • 3.2.2 其他方案調研:SO_REUSEPORT

    • 3.2.3 思考

    • 發布流程

    • 存在的問題

    • 3.1.1 發布流程

    • 3.1.2 存在的問題

    • 3.1 當前發布方式及存在的問題

    • 3.2 不損失容量、快速且用戶無感的發布

    • 3.3 新方案:bpf_sk_reuseport

  • 4 討論

    • 4.1 遇到的問題:CPU 毛刺(CPU spikes)甚至卡頓

    • 4.2 Listening socket hashtable

    • 4.3?bpf_sk_select_reuseport?vs?bpf_sk_lookup


1 引言

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

  • 從 LB 節點負載均衡到特定主機

  • 主機內:將流量負載均衡到不同socket

  • 以上兩個階段都涉及到流量的一致性路由(consistent routing of packets)問題。本文介紹這一過程中面臨的挑戰,以及我們如何基于最新的 BPF/XDP 特性來應對這些挑戰。

    1.1 前期工作

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

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

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

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

    1.2 Facebook 流量基礎設施

    從層次上來說,如下圖所示,Facebook 的流量基礎設施分為兩層:

  • 邊界層(edge tiers),位于 PoP 點

  • 數據中心層,我們稱為 Origin DC

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

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

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

  • 用戶連接(user connections)在邊界終結,

  • Edge PoP LB 將 L7 流量路由到終端主機,

  • Origin DC LB 再將 L7 流量路由到最終的應用,例如 HHVM 服務。

  • 1.3 面臨的挑戰

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

  • 宏觀層面:LB 節點 -> 后端主機

  • 微觀層面(主機內):主機內核 -> 主機內的不同 socket

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

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

  • BPF TCP header options[3]:解決主機外(宏觀)負載均衡問題;

  • BPF_PROG_TYPE_SK_REUSEPORT[4](及相關 map 類型?BPF_MAP_TYPE_REUSEPORT_SOCKARRAY):解決主機內(微觀)負載均衡問題。

  • 2 選擇后端主機:數據中心內流量的一致性與無狀態路由(四層負載均衡)

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

    2.1 Katran (L4LB) 負載均衡機制

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

    katran 是基于 XDP 實現的四層負載均衡器,它的內部機制:

    • 實現了一個 Maglev Hash 變種,通過一致性哈希選擇后端;

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

    用偽代碼來表示 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)。當一部分后端發生故障時,其他后端的哈希表項不受影響(因此對應的連接及主機也不受影響)。Maglev 論文中已經給出了評估這種容錯性的指標,如下圖,

    Resilience of Maglev hashing to backend changes

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

    • 橫軸表示 backend 掛掉的百分比

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

    Google 放這張圖是想說明:一部分后端發生變化時,其他后端受影響的概率非常小;但從我們的角度來說,以上這張圖說明:即使后端掛掉的比例非常小, 整個哈希表還是會受影響,并不是完全無感知 —— 這就會 導致一部分流量被錯誤路由(misrouting):

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

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

    2.2.2 TCP 長連接面臨的問題

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

    int?pick_host(packet*?pkt)?{if?(is_in_local_cache(pkt))???????????????//?場景一:ECMP shuffle 時(例如 LB 節點維護或故障),這里會 missreturn?local_cache[pkt]return?consistent_hash(pkt)?%?server_ring?//?場景二:后端維護或故障時,這里的好像有(較小)概率發生變化 }

    解釋一下:

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

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

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

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

    2.2.3 QUIC 協議為什么不受影響

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

    connection_id

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

    Facebook 已經廣泛使用 QUIC 協議,因此在 Facebook 內部,我們可以

  • 在 server 端將路由信息(routing information)嵌入到?connection_id?字段,并

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

  • 完全無狀態四層路由

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

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

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

    2.3.1 原理和流程

    基本思想:

  • 編寫一段?BPF_PROG_TYPE_SOCK_OPS?類型的 BPF 程序,attach 到 cgroup:

    • 在 LISTEN, CONNECT, CONN_ESTD 等事件時會觸發 BPF 程序的執行

    • BPF 程序可以獲取包的 TCP Header,然后往其中寫入路由信息(這里是 server_id),或者從中讀取路由信息

    在 L4LB 側維護一個 server_id 緩存,記錄仍然存活的 backend 主機

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

  • 客戶端發起一個 SYN;

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

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

    • 客戶端主機收到包后,會解析這個 id 并存下來,后面發包時都會帶上這個 server_id;

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

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

  • 客戶端流量將被(數據中心基礎設施)轉發到另一臺 L4LB;

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

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

    2.3.2 開銷

    數據開銷:TCP header 增加 6 個字節
    struct?tcp_opt?{uint8_t??kind;uint8_t??len;uint32_t?server_id; };?//?6-bytes?total
    運行時開銷:不明顯

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

    2.3.3 實現細節

    監聽的 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 -> server_id 的映射

    在每個 LB 節點上用 bpf_sk_storage 來存儲 per-flow server_id。也就是說,

  • 對于建連包特殊處理,

  • 建連之后會維護有 flow 信息(例如連接跟蹤),

  • 對于建連成功后的普通流量,從 flow 信息就能直接映射到 server_id, 不需要針對每個包去解析 TCP header。

  • server_id 的分配和同步

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

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

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

    2.3.4 效果

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

    2.3.5 限制

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

    • 對典型的數據中心內部訪問比較有用;

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

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

    2.4 小結

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

    • 這是一個完全無狀態的方案

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

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

    3 選擇 socket:服務的真正優雅發布(七層負載均衡)

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

    這部分內容在 SIGCOMM 2020 論文中有詳細介紹。想了解細節的可參考:

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

    3.1 當前發布方式及存在的問題

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

    3.1.1 發布流程

  • 發布前狀態:Proxygen 實例上有一些老連接,也在不斷接受新連接,

  • 拉出:拉出之后的實例不再接受新連接,但在一定時間窗口內,繼續為老連接提供服務;

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

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

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

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

  • 部署新代碼,

  • 關閉現有進程,創建一個新進程運行新代碼。

  • 監聽并接受新連接:升級之后的 Proxygen 開始正常工作, 最終達到和升級之前同等水平的一個連接狀態。

  • 3.1.2 存在的問題

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

  • 發布過程中,系統容量會降低。

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

  • 發布周期太長

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

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

  • 3.2 不損失容量、快速且用戶無感的發布

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

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

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

    發布流程

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

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

    • UDP socket,bind 在 VIP 上

    接下來開始發布:

  • 創建一個新實例

  • 將 TCP listening socket 和 UDP VIP 遷移到新實例;老實例仍然 serving 現有 TCP 連接(1 ~ N),

  • 新實例開始接受新連接(N+1 ~ +∞),包括新的 TCP 連接和新的 UDP 連接

  • 老實例等待 drain

  • 可以看到,這種方式:

  • 在發布期間不會導致系統容器降低,因為我們完全保留了老實例,另外創建了一個新實例

  • 發布速度可以顯著加快,因為此時可以并發發布多個實例

  • 老連接被 reset 的概率可以大大降低,只要允許老實例有足夠的 drain 窗口

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

    存在的問題

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

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

    • TCP 的狀態維護在內核。

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

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

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

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

    3.2.2 其他方案調研:SO_REUSEPORT

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

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

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

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

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

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

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

    3.2.3 思考

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

  • 在內核中實現流量的無損切換,以便客戶端完全無感知;

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

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

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

    • 實現上:仍然基于 SO_REUSEPORT 思想,但同時解決 UDP 的一致性路由和瓶頸問題。

    最終我們引入了一個 socket 層負載均衡器 bpf_sk_reuseport。

    3.3 新方案:bpf_sk_reuseport

    3.3.1 方案設計

    簡單來說,

  • 在 socket 層 attach 一段 BPF 程序,控制 TCP/UDP 流量的轉發(負載均衡):

  • 通過一個 BPF map 維護配置信息,業務進程 ready 之后自己配置流量切換。

  • 3.3.2 好處

    這種設計的好處:

  • 通用,能處理多種類型的協議。

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

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

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

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

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

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

  • 3.3.3 發布過程中的流量切換詳解

    用一個?BPF_MAP_TYPE_REUSEPORT_SOCKARRAY?類型的 BPF map 來配置轉發規則,其中,

    • key:<VIP>:<Port>

    • value:socket 的文件描述符,與業務進程一一對應

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

    BPF 就繼續將所有流量轉給老進程,

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

    BPF 程序就開始將流量轉發給新進程了:

    前面沒提的一點是:我們仍然希望將 UDP 包轉發到老進程上,這里實現起來其實就非常簡單了:

  • 已經維護了 flow -> socket 映射

  • 如果 flow 存在,就就轉發到對應的 socket;不存在在創建一個新映射,轉發給新實例的 socket。

  • 這也解決了擴展性問題,現在可以并發接收包(one-thread-per-socket),不用擔心新進程啟動時的 disruptions 或 misrouting 了:

    3.3.4 新老方案效果對比

    先來看發布過程對業務流量的擾動程度。下圖是我們的生產數據中心某次發布的統計,圖中有兩條線:

    • 一條是已發布的 server 百分比,

    • 另一個條是同一時間的丟包數量,

    可以看到在整個升級期間,丟包數量沒有明顯變化。

    再來看流量分發性能,分別對 socket takeover 和 bpf_sk_reuseport 兩種方式加壓:

    • 控制組/對照組(左邊):3x 流量時開始丟包,

    • 實驗組(右邊):30x,因此還沒有到分發瓶頸但 CPU 已經用滿了,但即使這樣丟包仍然很少。

    3.3.5 遇到的坑

    生產環境遇到過一個嚴重問題:新老進程同時運行期間,觀察到 CPU spike 甚至 host locking;但測試環境從來沒出現過,而且在實現上我們也沒有特別消耗 CPU 的邏輯。

    排查之后發現,這個問題跟 BPF 程序沒關系,直接原因是

  • 在同一個 netns 內有大量 socket,

  • 新老實例同時以支持和不支持 bpf_sk_reuseport 的方式 bind 到了同一端口,

    bind("[::1]:443");?/*?without?SO_REUSEPORT.?Succeed.?*/ bind("[::2]:443");?/*?with????SO_REUSEPORT.?Succeed.?*/ bind("[::]:443");??/*?with????SO_REUSEPORT.?Still?Succeed?*/
  • bind() 實現中有一個 spin lock 會遍歷一個很長的 hashtable bucket,

    如果有大量 http endpoints,那 key 很可能就是 ?443 和 80;這會導致 CPU 毛刺甚至機器卡住。

  • 這個問題花了很長時間排查,因此有人在類型場景下遇到類似問題,很可能跟這個有關。相關內核代碼[7], 修復見?patch[8]

    3.3.6?bpf_sk_select_reuseport?vs?bpf_sk_lookup

    Cloudflare 引入了?`bpf_sk_lookup`[9]

    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?listening socket?for?a?new?connection?request?(TCP),?or?when?looking?up?an unconnected?socket?for?a?packet?(UDP).This?serves?as?a?mechanism?to?overcome?the?limits?of?what?bind()?API?allows to?express.?Two?use-cases?driving?this?work?are:(1)?steer?packets?destined?to?an?IP?range,?fixed?port?to?a?single?socket192.0.2.0/24,?port?80?->?NGINX?socket(2)?steer?packets?destined?to?an?IP?address,?any?port?to?a?single?socket198.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,因此與?bpf_sk_select_reuseport功能有些重疊,因為二者都源于這樣一種限制:在收包時,缺少從應用層直接命令內核選擇哪個 socket 的控制能力。

    但二者也是有區別的:

    • sk_select_reuseport?與 IP 地址所屬的 socket family 是緊耦合的

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

    3.3.7 小結

    本節介紹了我們的基于 BPF_PROG_TYPE_SK_REUSEPORT 和 BPF_MAP_TYPE_REUSEPORT_SOCKARRAY 實現的新一代發布技術,它能實現主機內新老實例流量的無損切換,優點:

  • 簡化了運維流程,去掉脆弱和復雜的進程間通信(IPC),減少了故障;

  • 效率大幅提升,例如 UDP 性能 10x;

  • 可靠性提升,例如避免了 UDP misrouting 問題和 TCP 三次握手時的競爭問題。

  • 引用鏈接

    [1]

    From XDP to Socket: Routing of packets beyond XDP with BPF:?https://linuxplumbersconf.org/event/11/contributions/950/

    [2]

    katran:?https://engineering.fb.com/2018/05/22/open-source/open-sourcing-katran-a-scalable-network-load-balancer/

    [3]

    BPF TCP header options:?https://lwn.net/Articles/827672/

    [4]

    BPF_PROG_TYPE_SK_REUSEPORT:?http://archive.lwn.net:8080/netdev/20180808080131.3014367-1-kafai@fb.com/t/

    [5]

    Zero Downtime Release: Disruption-free Load Balancing of a Multi-Billion User Website:?https://dl.acm.org/doi/pdf/10.1145/3387514.3405885

    [6]

    SO_REUSEPORT:?https://lwn.net/Articles/542629/

    [7]

    代碼:?https://github.com/torvalds/linux/blob/v5.10/net/ipv4/inet_connection_sock.c#L376

    [8]

    patch:?https://lore.kernel.org/lkml/20200601174049.377204943@linuxfoundation.org/

    [9]

    bpf_sk_lookup:?https://lwn.net/Articles/825103/

    原文:https://arthurchiao.art/blog/facebook-from-xdp-to-socket-zh/

    - END -


    看完一鍵三連在看轉發點贊

    是對文章最大的贊賞,極客重生感謝你

    推薦閱讀

    定個目標|建立自己的技術知識體系


    大廠后臺開發基本功修煉路線和經典資料

    Linux網絡新技術基石 |eBPF and XDP

    性能優化實戰|使用eBPF代替iptables優化服務網格數據面性能


    萬字長文|深入理解XDP全景指南


    你好,這里是極客重生,我是阿榮,大家都叫我榮哥,從華為->外企->到互聯網大廠,目前是大廠資深工程師,多次獲得五星員工,多年職場經驗,技術扎實,專業后端開發和后臺架構設計,熱愛底層技術,豐富的實戰經驗,分享技術的本質原理,希望幫助更多人蛻變重生,拿BAT大廠offer,培養高級工程師能力,成為技術專家,實現高薪夢想,期待你的關注!點擊藍字查看我的成長之路

    校招/社招/簡歷/面試技巧/大廠技術棧分析/后端開發進階/優秀開源項目/直播分享/技術視野/實戰高手等,?極客星球希望成為最有技術價值星球,盡最大努力為星球的同學提供面試,跳槽,技術成長幫助!詳情查看->極客星球

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 求點贊,在看,分享三連

    總結

    以上是生活随笔為你收集整理的eBPF在大厂的应用的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。