在前一篇《如何實現核心旁路(Kernel bypass)?》 中,我們討論了 Linux 核心網路協議棧的效能瓶頸。我們詳細說明了如何利用核心旁路(kenerl bypass)技術,讓使用者空間程式可以接收大量的資料包。遺憾的是,沒有開源解決方案討論過這個問題,來滿足我們的需求。為了改善現狀,我們決定為 Netmap 專案 做點貢獻。在本篇文章中將描述我們提出的改動。
Binary Koala 拍攝,CC BY-SA 2.0
我們的需求
在 CloudFlare,我們經常處理洶湧的資料流量。我們的網路經常會接收到很多的資料包,它們總是來自大量的同步攻擊。事實上此刻僅是這篇文章所在的伺服器,都完全有可能每秒鐘處理數百萬的資料包。
因為 Linux 核心實際上不能真正處理大量的資料包,我們需要想辦法解決這個問題。在洪水攻擊期間,我們將選定的網路流量解除安裝到使用者空間程式。這個應用程式以非常高的速度過濾資料包。大部分資料包被丟棄了,因為它們屬於洪水攻擊。少量“有效”的資料包被重新返回核心並且以正常流量的方式處理。
需要重點強調的是核心旁路技術只針對選定的流,這意味著所有其它的資料包都像往常一樣經過核心。
這個工作完美地建立在基於 Solarflare 網路卡的伺服器上 —— 我們可以使用 ef_vi API 來實現核心旁路技術。不幸的是,我們沒有在基於Intel IXGBE 網路卡的伺服器上實現這個功能。
直到 Netmap 出現。
Netmap
過去的幾個月裡,我們一直認真地思考如何在非 Solarflare 網路卡上旁路選定的資料流(又叫做:分叉驅動)。
我們考慮過 PF_RING,DPDK 和其他一些定製的解決方案,但不幸的是所有這些方案都應用在整個網路卡上(譯者注:即不能在選定的資料流上操作),最後我們決定最好的方式就是根據我們需要的功能給 Netmap 打補丁。
- 它完全開源並且基於 BSD 許可釋出。
- 它可以很好得支援網路卡無關 API。
- 它非常快:很容易滿足線路速率的要求。
- 這個專案維護得很好並且相當成熟。
- 程式碼的質量非常高。
- 對特定驅動的修改很小:大多數神奇的事都發生在共享的 Netmap 模組,很容易對新增的新硬體提供支援。
單 RX 佇列模組介紹
我們不希望這樣。我們想讓大部分 RX 佇列留在核心模式中,僅僅在選定的 RX 佇列上採用 Netmap 模式,我們把這個功能稱為:“單 RX 佇列模式”。
- 在“單 RX 佇列模式”下開啟一個網路介面。
- 這將允許 Netmap 應用從特定的 RX 佇列上接收資料包。
- 其他所有佇列都被繫結在主機網路協議棧上。
- 按需新增或刪除“單 RX 佇列模式”下的 RX 佇列。
- 最後從 Netmap 模式上移除這個介面,並將 RX 佇列重新繫結到主機協議棧上。
這個 Netmap 的補丁正在等待程式碼審查,可以在這裡找到它:
一個從 eth3 的 RX 4 號佇列上接收資料包的小程式看起來像這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
d = nm_open("netmap:eth3~4", NULL, 0, 0); while (1) { fds = {fds: d->fd, events: POLLIN}; poll(&fds, 1, -1); ring = NETMAP_RXRING(d->nifp, 4); while (!nm_ring_empty(ring)) { i = ring->cur; buf = NETMAP_BUF(ring, ring->slot[i].buf_idx); len = ring->slot[i].len; //process(buf, len) ring->head = ring->cur = nm_ring_next(ring, i); } } |
這段程式碼非常接近 Netmap 的示例程式。事實上唯一區別在於 nm_open() 呼叫,它使用了新的語法,netmap:ifname~queue_number。
再次,當執行這段程式碼時只要資料包到達 4 號 RX 佇列就會進入 netmap 程式,所有其他的 RX 和 TX 佇列將被 Linux 核心網路協議棧處理。
你可以在這裡找到更多完整的例子:
獨立一個佇列
由於 RSS(譯者注:Receive Side Scaling,由微軟提出,通過這項技術能夠將網路流量分散到多個 cpu 上,降低單個 cpu 的佔用率) 的存在,多佇列網路卡上的一個資料包可以出現在任意一個 RX 佇列上。這是為什麼開啟單 RX 模式時,必須確保只有選定的流才可以進入 Netmap 佇列。
這些是必須做的:
- 修改間接表以確保沒有 RSS 雜湊的資料包到達這裡。
- 使用流控制技術專門引導資料流到達獨立的佇列中。
- 用 RFS(譯者注:Receive Flow Steering,接收流控制)方法解決 —— 確保即將執行 Netmap 的 CPU 上沒有其他程式執行。
舉個例子:
1 2 3 |
$ ethtool -X eth3 weight 1 1 1 1 0 1 1 1 1 1 $ ethtool -K eth3 ntuple on $ ethtool -N eth3 flow-type udp4 dst-port 53 action 4 |
這裡我們設定間接表以阻止前往 4 號 RX 佇列的流量,接著我們使用流控制技術,將所有目的埠是 53 的 UDP 流量引導向 4 號佇列。
嘗試一下
以下是如何在 IXGBE 網路卡上執行這項技術。首先獲取原始碼:
1 2 3 4 |
$ git clone https://github.com/jibi/netmap.git $ cd netmap $ git checkout -B single-rx-queue-mode $ ./configure --drivers=ixgbe --kernel-sources=/path/to/kernel |
載入 Netmap 補丁模組並設定介面:
1 2 3 4 5 6 7 8 |
$ insmod ./LINUX/netmap.ko $ insmod ./LINUX/ixgbe/ixgbe.ko $ # Distribute the interrupts: $ (let CPU=0; cd /sys/class/net/eth3/device/msi_irqs/; for IRQ in *; do echo $CPU > /proc/irq/$IRQ/smp_affinity_list; let CPU+=1 done) $ # Enable RSS: $ ethtool -K eth3 ntuple on |
現在我們開始以 6 M UDP 資料包來填充這個介面,htop 命令顯示伺服器此刻完全忙於處理大量的資料包:
為了應對大流量,我們啟動 Netmap。首先我們需要編輯間接表,挑選出 4 號 RX 佇列:
1 2 |
$ ethtool -X eth3 weight 1 1 1 1 0 1 1 1 1 1 $ ethtool -N eth3 flow-type udp4 dst-port 53 action 4 |
這導致所有的資料包都去往 4 號 RX 佇列。
在 Netmap 設定介面前,必須關閉硬體解除安裝的功能:
1 |
$ ethtool -K eth3 lro off gro off |
最後我們啟動 netmap 解除安裝功能:
1 2 3 4 5 6 7 8 9 10 11 12 |
$ sudo taskset -c 15 ./nm_offload eth3 4 [+] starting test02 on interface eth3 ring 4 [+] UDP pps: 5844714 [+] UDP pps: 5996166 [+] UDP pps: 5863214 [+] UDP pps: 5986365 [+] UDP pps: 5867302 [+] UDP pps: 5964911 [+] UDP pps: 5909715 [+] UDP pps: 5865769 [+] UDP pps: 5906668 [+] UDP pps: 5875486 |
正如你看到的,基於單 RX 佇列的 netmap 程式大約收到 5.8M 資料包。
為了完整性,這有一個僅執行在單核下的 netmap 的 htop 展示:
感謝
我要感謝 Pavel Odintsov,是他提出了使用 Netmap 這種方式的可能性。他還做了初步的修改,使得我們在基礎上進行開發。
我也要感謝 Luigi Rizzo,為他對 Netmap 所做的工作和對我們補丁提出很棒的意見。
結束語
在 CloudFalre ,我們應用程式協議棧是以開源軟體為基礎的。我們對開源工作者所做出的卓越工作表示感激,無論何時我們會盡力回報社群 —— 我們希望 “單 RX 佇列 Netmap 模式”對其他人能有所幫助。
你可以在 這裡 找到更多關於 CloudFlare 的開原始碼。
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!
任選一種支付方式