全面適配DPDK 20.11,DPVS發佈v1.9.0版本

經過 DPVS 團隊和社區開發者一個多季度的開發迭代,愛奇藝開源項目DPVS已經正式發佈了v1.9.0版本。DPVS v1.9.0 正式發佈於2021/9/1,它適配了當前DPDK 穩定版本DPDK-20.11(LTS),支持 DPDK API/ABI 以及多種設備驅動的更新和優化。目前 DPVS v1.9.0 已在愛奇藝的多個核心數據中心部署上線,且穩定運行三個月。


01

   關於 DPVS


DPVS 是愛奇藝網絡虛擬化團隊基於DPDK (Data Plane Development Kit)和 LVS (Linux Virtual Server) 開發的高性能四層網絡軟件負載均衡器,支持 FullNAT /DR /Tunnel /SNAT /NAT64 /NAT 六種負載均衡轉發方式和IPv4 /IPv6 /TCP /UDP /ICMP /IMCPv6 等多種網絡協議,單核性能達到 2.3M PPS(每秒轉發 230 萬個包),單機性能可以達到萬兆網卡線速(約爲 15M PPS)。愛奇藝的四層負載均衡服務、SNAT 代理服務幾乎全部都是基於 DPVS 實現的。此外,DPVS 於2017 年 10 月開源後,已吸引了來自包括網易、小米、中國移動、Shopee、字節跳動等在內的國內外衆多知名企業的核心貢獻者參與社區共建。


項目地址:

https://github.com/iqiyi/dpvs


使用文檔:

https://github.com/iqiyi/dpvs/blob/master/doc/tutorial.md


02

   DPVS v1.9.0 內容更新列表


發佈地址:

https://github.com/iqiyi/dpvs/releases/tag/v1.9.0


DPVS 整個 1.9 大版本都將基於 DPDK 20.11 開發,v1.9.0 版本核心更新就是全面適配了 DPDK-20.11(LTS)。對 DPDK-18.11(LTS) 的支持已經移動到 DPVS-1.8-LTS中,同時終止了對DPDK-17.11(LTS) 的支持。


注:DPVS v1.9.0 使用的 DPDK 具體版本號是DPDK 20.11.1


DPVS v1.9.0 是在 v1.8.10 基礎上開發的,其主要的內容更新分爲功能更新和漏洞修復兩類,分別列舉如下。


2.1  功能更新


  • Dpvs: 新增 flow 管理功能,使用通用的 rte_flow API 替換了基於 flow director 的流管理機制。

  • Dpvs: Mbuf 用戶自定義數據分類管理,使用 dynfiels 實現了 mbuf 內部用戶自定義數據的分類管理。

  • Dpvs: 適配 DPDK 20.11 數據類型,優化 DPVS 協議棧處理。

  • Dpvs: 優化 Makefile,適配 DPDK 20.11 meson/ninja 構建機制。

  • Dpvs: 增加"dedicated_queues" 配置選項,支持 802.3ad 網卡綁定模式下 LACP 包專用隊列可配置。

  • Dpdk: 移植多個補丁文件到 DPDK 20.11,並廢棄 DPDK 18.11 和 DPDK 17.11 的補丁文件。

  • Dpdk: 優化 DPDK 部署和安裝,支持 DPVS 開發環境的快速構建。

  • Keeaplived: 增加 UDP_CHECK 健康檢查方法,提高了 UDP 業務健康檢查的可靠性和效率。

  • Docs: 更新文檔,適配 DPDK 20.11。

  • CI: 更新 GitHubworkflow,支持 DPDK 20.11 (DPVS v1.9) 和 DPDK 18.11(DPVS 


2.2  漏洞修復


  • Dpvs: 修復 rr/wrr/wlc 調度算法不同 RS 上負載不均問題。

  • Dpvs: 修復 802.3ad 網卡綁定模式下 Mellanox 25G 網卡不通的問題。

  • Dpdk: 修復 DPDK ixgbe PMD 驅動無法支持 DPVS 的 flow 配置問題。

  • Dpdk: 修復 DPDK mellanox PMD 驅動在調試工作模式下程序崩潰問題。


03

   DPVS v1.9.0 重點更新介紹



3.1  更友好的編譯使用安裝方式


DPDK 20.11 用 meson/ninja徹底取代了之前版本的 Makefile 構建方式,而 DPVSv1.9.0 雖然繼續沿用了 Makefile 構建方式,但是適配了 DPDK 20.11 的構建方式,通過 pkg-config工具自動查找依賴 DPDK 的頭文件和庫文件,解決了 DPVS 安裝時複雜的環境依賴問題,使得 DPVS 構建更加智能。


CFLAGS += -DALLOW_EXPERIMENTAL_API $(shell pkg-config --cflags libdpdk)LIBS += $(shell pkg-config --static --libs libdpdk)


完整的文件請參考 dpdk.mk文件。可以看到,DPVS 鏈接階段使用了 DPDK 靜態庫。這雖然增加了 DPVS 可執行程序的大小,但避免了 DPVS 運行時在系統裏安裝 DPDK 動態鏈接庫的需求;同時,由於 DPVS 對 DPDK 打了一些補丁,用靜態鏈接的方式也避免了 DPDK 動態鏈接庫安裝時可能出現的版本衝突的麻煩。


爲了簡化 DPVS 的編譯安裝流程,DPVS v1.9.0 提供了一個輔助腳本 dpdk-build.sh,其用法如下。


$ ./scripts/dpdk-build.sh -husage: ./scripts/dpdk-build.sh [-d] [-w work-directory] [-p patch-directory]OPTIONS:   -v   specify the dpdk version, default 20.11.1   -d   build dpdk libary with debug info   -w   specify the work directory prefix, default {{ pwd }}    -p   specify the dpdk patch directory, default {{ pwd }}/patch/dpdk-stable-20.11.1


這個腳本參數支持用戶指定編譯 DPDK 使用的工作目錄前綴、DPDK patch 文件所在的目錄、DPDK 版本號(目前僅支持 20.11.1)、是否編譯爲 DEBUG 版本,其主要的工作流程如下:


  • 從 DPDK 官網下載指定版本的 DPDK 壓縮包(需要訪問公網)到用戶指定的工作目錄裏,如果目錄裏已存在則跳過下載直接使用;

  • 解壓 DPDK 包到工作目錄下中;

  • 打上 DPVS 提供的所有補丁文件

  • 在當前目錄的 dpdkbuild 子目錄下編譯 DPDK,編譯完成後安裝到 dpdklib 子目錄下;

  • 給出 PKG_CONFIG_PATH 環境變量配置方法。


利用這個輔助腳本,編譯 DPVS 僅需要如下三個簡單步驟:


S1. 編譯安裝 DPDK


$ ./scripts/dpdk-build.sh -d  -w /tmp -p ./patch/dpdk-stable-20.11.1/...DPDK library installed successfully into directory: //tmp/dpdk/dpdklibYou can use this library in dpvs by running the command below:
export PKG_CONFIG_PATH=//tmp/dpdk/dpdklib/lib64/pkgconfig

注:爲了說明腳本的用法,本例的命令是在 /tmp/dpdk 目錄裏編譯安裝有調試信息的 DPDK 版本。通常情況下腳本不用指定參數,使用默認值即可。


S2. 根據腳本輸出提示設置環境變量


$ export PKG_CONFIG_PATH=/tmp/dpdk/dpdklib/lib64/pkgconfig


S3. 編譯安裝 DPVS


$ make && make install

DPVS 默認安裝在當前目錄的 ./bin 子目錄下


3.2  更通用的流(flow)配置管理


DPVS FullNAT 和 SNAT 的多核轉發需要配置網卡的流處理規則。下圖是一個典型的 DPVS 雙臂模式部署形式,DPVS 服務器有兩個網卡接口:網卡-1 負責和用戶通信,網卡-2 負責和 RS 通信。一般地,如果服務是 FullNAT,連接由外網用戶發起,網卡-1 是外網網卡,網卡-2 是內網網卡;如果服務是 SNAT,連接由用戶從內網發起,網卡-1 是內網網卡,網卡-2 是外網網卡。



Inbound(用戶到 RS)方向的流量通過 RSS 分發到不同的 worker 線程上,而 Outbound(RS 到用戶)的流量通過網卡流處理規則保證同一個會話的流量能匹配到正確的 worker 線程。DPVS v1.8 及其之前的版本使用 DPDK 的 rte_eth_dev_filter_ctrl接口配置 Flow Director 類型(RTE_ETH_FILTER_FDIR)的流規則以實現 Outbound 方向的數據流和 Inbound 方向數據流的會話匹配。但是,DPDK 20.11 徹底廢棄了rte_eth_dev_filter_ctrl接口,改用 rte_flow 屏蔽了不同網卡、不同類型的流規則實現細節,實現了一種更通用的網卡流規則配置接口。因此,DPVS v1.9.0 適配了rte_flow這種新的流配置接口。

rte_flow 接口需要提供一組 flow item 組成的 pattern 和一組 action。如果數據包和流規則中的 pattern 匹配,則 action 的配置會決定數據包的下一步處理方式,比如送到某個網卡隊列、打上標籤、或者丟棄。因爲 DPVS 不僅支持物理設備接口,而且支持 Bonding、VLAN 等虛接口設備,所以我們增加了 netif_flow 模塊來管理 DPVS 不同類型的設備的 rte_flow 流規則。功能上,目前主要提供了 sa_pool 的操作接口,用於實現上面所述的兩個方向流的會話匹配。


/** Add sapool flow rules (for fullnat and snat).** @param dev [in]*     Target device for the flow rules, supporting bonding/physical ports.* @param cid [in]*     Lcore id to which to route the target flow.* @param af [in]*     IP address family.* @param addr [in]*     IP address of the sapool.* @param port_base [in]*     TCP/UDP base port of the sapool.* @param port_mask [in]*     TCP/UDP mask mask of the sapool.* @param flows [out]*     Containing netif flow handlers if success, undefined otherwise.** @return*     DPVS error code.*/int netif_sapool_flow_add(struct netif_port *dev, lcoreid_t cid,           int af, const union inet_addr *addr,           __be16 port_base, __be16 port_mask,           netif_flow_handler_param_t *flows);
/** Delete saflow rules (for fullnat and snat).* @param dev [in]* Target device for the flow rules, supporting bonding/physical ports.* @param cid [in]* Lcore id to which to route the target flow.* @param af [in]* IP address family.* @param addr [in]* IP address of the sapool.* @param port_base [in]* TCP/UDP base port of the sapool.* @param port_mask [in]* TCP/UDP mask mask of the sapool.* @param flows [in]* Containing netif flow handlers to delete.** @return* DPVS error code.*/int netif_sapool_flow_del(struct netif_port *dev, lcoreid_t cid, int af, const union inet_addr *addr, __be16 port_base, __be16 port_mask, netif_flow_handler_param_t *flows);
/** Flush all flow rules on a port. ** @param dev* Target device, supporting bonding/physical ports.** @return* DPVS error code.*/int netif_flow_flush(struct netif_port *dev);

說明:Bonding 802.3ad 模式的 dedicatedqueue 也是通過 rte_flow 配置的,如果使用了這個功能,請注意不能隨意調用 rte_flow_flush 或 netif_flow_flush。


具體到 rte_flow 的配置上,sa_pool 的 flow pattern 匹配的是目標 IP 地址和目標端口信息。爲了減少網卡中流的數量,我們把目標端口地址空間,即 0 ~ 65535,按照 DPVS 配置的 worker 數量,設置了非全地址空間的掩碼。基本思路是把端口地址空間等分爲worker數量的份數,每個 worker 關聯其中一份端口地址子空間。所以,假如有 8 個 worker,我們僅需要配置3-bit 的端口地址掩碼,數據包的目標端口地址和 flow item 中指定的端口地址掩碼進行 ”與”操作後得到的結果與 flowitem 中的端口基值比較,如果相等,則將數據包送到對應 action 設置的網卡隊列中。下面是 DPVS sa_pool 的 flow pattern 和 action 的具體配置。


需要說明的是,rte_flow 僅給我們提供了網卡流規則配置的統一接口,具體的流規則能否支持仍依賴於網卡硬件功能以及網卡的 DPDK PMD 驅動。目前,我們已經驗證 Mellanox ConnextX-5(mlx5)可以支持 DPVS 的sa_pool flow 配置。Intel 82599 系列網卡(ixgbe 驅動)的雖然硬件支持 Flow Director,但是其 DPDK PMD 驅動卻沒有適配好 rte_flow 接口,甚至在 Debug 模式下出現因非法內存訪問導致程序崩潰的問題,所以我們給 ixgbe PMD驅動開發了補丁 0004-ixgbe_flow-patch-ixgbe-fdir-rte_flow-for-dpvs.patch ,使其也成功支持了 DPVS 的流處理規則。其它更多的網卡類型仍有待 DPVS 使用者的驗證。


3.3  更合理的 mbuf 自定義數據


爲了提高效率,DPVS 使用 DPDK 的 mbuf 用戶自定義空間存儲與數據包相關的、需要被多個模塊使用的關鍵數據。目前,DPVS 在 mbuf 中存儲的數據有兩種類型:路由信息和 IP header 指針。DPDK 18.11 中 mbuf 的用戶自定義數據空間長度是 8 個字節,在 64 位機器上最多隻能存儲一個指針數據,DPVS 需要小心區分兩種數據的存放和使用時機,保證它們不衝突。DPDK 20.11 的 mbuf 用 dynamic fields取代了 userdata,並將長度增加到 36 個字節,且提供了一組API讓開發者動態註冊和申請使用。DPVS v1.9.0 爲兩種用戶數據申請了獨立的存儲空間,開發者不用再擔心數據衝突的問題了。



爲了利用 mbuf 的dynamic fields機制,DPVS 定義了兩個宏。


#define MBUF_USERDATA(m, type, field) \   (*((type *)(mbuf_userdata((m), (field)))))
#define MBUF_USERDATA_CONST(m, type, field) \   (*((type *)(mbuf_userdata_const((m), (field)))))


其中,m表示 DPDK 的 mbuf 數據包結構,type是 DPVS 用戶數據的類型,field是 DPVS 定義的用戶數據類型的枚舉值。


typedef enum {   MBUF_FIELD_PROTO = 0,   MBUF_FIELD_ROUTE,} mbuf_usedata_field_t;mbuf_userdata(_const)


通過 mbuf 用戶數據註冊時返回的地址偏移量獲取存儲在 dynamic fields 裏的用戶數據。


#define MBUF_DYNFIELDS_MAX   8static int mbuf_dynfields_offset[MBUF_DYNFIELDS_MAX];
void *mbuf_userdata(struct rte_mbuf *mbuf, mbuf_usedata_field_t field){ return (void *)mbuf + mbuf_dynfields_offset[field];}
void *mbuf_userdata_const(const struct rte_mbuf *mbuf, mbuf_usedata_field_t field){ return (void *)mbuf + mbuf_dynfields_offset[field]; }


最後,我們在 DPVS 初始化時調用 DPDK 接口 rte_mbuf_dynfield_register,初始化 mbuf_dynfields_offset偏移量數組。


int mbuf_init(void){   int i, offset;
const struct rte_mbuf_dynfield rte_mbuf_userdata_fields[] = { [ MBUF_FIELD_PROTO ] = { .name = "protocol", .size = sizeof(mbuf_userdata_field_proto_t), .align = 8, }, [ MBUF_FIELD_ROUTE ] = { .name = "route", .size = sizeof(mbuf_userdata_field_route_t), .align = 8, }, };
for (i = 0; i < NELEMS(rte_mbuf_userdata_fields); i++) { if (rte_mbuf_userdata_fields[i].size == 0) continue; offset = rte_mbuf_dynfield_register(&rte_mbuf_userdata_fields[i]); if (offset < 0) { RTE_LOG(ERR, MBUF, "fail to register dynfield[%d] in mbuf!\n", i); return EDPVS_NOROOM; } mbuf_dynfields_offset[i] = offset; }
return EDPVS_OK;}


3.4  更完善的調度算法


長連接、低併發、高負載的 gRPC 業務反饋 DPVS 在他們這種應用場景下,連接數量在 RS 上分佈不均勻。經排查分析,這個問題是由 rr/wrr/wlc 調度算法的 per-lcore 的實現方式導致的。如下圖所示,假設 DPVS 配置了 8 個轉發 worker,inbound方向(用戶到 RS 方向)的流量是通過網卡 RSS HASH 功能,將流量分發到 w0...w7 不同 worker 上。



因爲每個 worker 上的調度算法和數據是相互獨立的,而且所有 worker 以相同的方式初始化,所以每個 worker 會以相同的順序選取 RS。比如,對於輪詢(rr)調度,所有 worker 上的第一個連接都會選擇 RS 列表中的第一臺服務器。下圖給出了 8 個 worker, 5 個 RS 的調度情況:假設 RSS HASH 算法是平衡的,則很可能前 8 個用戶連接分別哈希到 8 個不同 worker 上,而 8 個 worker 獨立調度,將 8 個用戶流量全都轉發到第一個 RS 上,而其餘 4 個 RS 沒有用戶連接,使得負載在 RS 上分佈很不均衡。



DPVS v1.9.0 解決了這個問題,思路很簡單,我們讓不同 worker 上的調度算法按照如下策略選擇不同的 RS 初始值:


 InitR(cid) = ⌊N(rs) × cid / N(worker)⌋


其中,N(rs)、N(worker) 分別是 RS 和worker 的數量,cid 是 worker 的編號(從 0 開始編號),InitR(cid)爲編號爲 cid 的 worker 調度算法的RS 初始值。下圖給出了上面的例子使用這種策略調度結果,用戶連接可以均衡的分佈到所有 RS 上了。



3.5  更高效的 keepalived UDP 健康檢

此前版本的 DPVS keepalived 不支持 UDP_CHECK,UDP 業務的健康檢查只能使用 MISC_CHECK 方式,這種方式的配置示例如下:

real_server 192.168.88.115 6000 {  MISC_CHECK {      misc_path "/usr/bin/lvs_udp_check 192.168.88.115 6000"      misc_timeout 3  }   }  


其中, lvs_udp_check 腳本通過 nmap 工具探測 UDP 端口是否開放。


ipv4_check $ipif [ $? -ne 0 ]; then  nmap -sU -n $ip -p $port | grep 'udp open' && exit 0 || exit 1else  nmap -6 -sU -n $ip -p $port | grep 'udp open' && exit 0 || exit 1fi


基於 MISC_CHECK 的 UDP 健康檢查方式有如下缺點:

  • 性能低,每次檢查需要啓動一個進程,並在新進程裏執行一個腳本,CPU 消耗大,一般只能支持數百個 RS 的情況。
  • 檢查不準確,一般只能探測到端口是否可用,不能根據實際業務情況配置。
  •  配置複雜,需要在系統裏額外安裝健康檢查腳本。
  • 檢查結果依賴外部工具,可靠性、一致性不能保證。


爲了支持高性能的 UDP 健康檢查,DPVS 社區開發者 weiyanhua100移植了最新 keepalived 官方版本的 UDP_CHECK 模塊到 DPVS 的 keepalived 中。這種方式的配置示例如下:

real_server 192.168.88.115 6000 {  UDP_CHECK {      retry 3      connect_timeout 5      connect_port 6000      payload hello      require_reply hello ok      min_reply_length 3      max_reply_length 16  }}


其中, payload指定健康檢查程序發送給 RS 的 UDP 請求數據,require_reply是期望收到的 RS 的 UDP 響應數據。這樣 UDP 服務器可以自定義健康檢查接口,通過這種方式,我們既能探測到 RS 上的 UDP 服務是否真的可用,也能避免健康檢查對真實業務的干擾。如果不指定 payload和 require_reply,則只進行 UDP 端口探測,效果和 nmap 端口探測方式類似。

UDP_CHECK 通過 keepalived 和 RS 之間的UDP 數據交互以及 ICMP 錯誤報文確定後端 UDP 服務的可用性,這種方式的優點如下。

  • 性能高,基於 epoll 多路複用模型,可以支持上萬個 RS 的健康檢查。
  • 不僅支持端口探測,而且支持業務探測。
  •  配置簡單,無外部依賴,使用方便。


04

   未來版本計劃

4.1   DPVS v1.8.12(2021 Q4)


  • 功能開發:ipset 功能模塊
  • 功能開發:基於 tc/ipset 的流量控制功能
  • 功能開發:基於 netfilter/ipset 訪問控制功能

4.2  DPVS v1.9.2(2022 Q1)


  • 性能優化:基於 rte_flow 實現 KNI 收包隔離,提高控制面的可靠性。
  • 性能優化:協議棧優化,減少數據包的重複解析計算。
  • 功能優化:優化二層組播地址管理問題,解決 KNI 接口組播地址覆蓋問題。
  • 功能優化:解決 keepalived 某些情況下無法加載新的配置的問題。
  • 性能測試:測試 v1.9.2 的性能,給出 25G 網卡的多核性能數據


4.3  長遠版本


  • 日誌優化,兼容 RTE_LOG,解決當前異步日誌崩潰問題,並支持分類、去重、限速。
  • FullNAT46 和 XOA 內核模塊,支持外部 IPv4 網絡訪問 IPv6 內網的場景。
  • DPVS 內存池設計,支持高性能、併發安全、動態伸縮、不同長度對象的存取。
  • 優化 DPVS 接口(netif_port)管理,解決多線程動態增刪接口的不安全問題。
  • RSS Precalculating,實現一種對硬件要求更低的數據流和worker的匹配方案。
  • Portless service,支持 "IP+任意端口" 類型的業務類型。


05

   參與社區

目前,DPVS 是一個由數十個公司的開發者、使用者參與的開源社區,我們非常歡迎對 DPVS 感興趣的同學參與到該項目的使用、開發和社區的建設、維護中來。歡迎大家爲 DPVS 提供任何方面的貢獻,不論是文檔,還是代碼;issue 還是 bug fix;以及,也非常歡迎大家把公司添加到 DPVS 社區用戶列表中。
如果你對 DPVS 有問題,可以通過如下幾種方式聯繫到我們。


看完心動了嗎?
戳👇“ 閱讀原文”直達招聘頁面
即刻加入愛奇藝!

也許你還想看
開源|iQiYi 高性能開源負載均衡器及應用
開源 | 愛奇藝網絡流量分析引擎QNSM及其應用
愛奇藝APP全面適配iOS 14 首批支持畫中畫功能 追劇聊天兩不誤

 關注我們,更多精彩內容陪伴你!

本文分享自微信公衆號 - 愛奇藝技術產品團隊(iQIYI-TP)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章