提高UDP互動效能
這是一篇個人認為非常非常厲害的文章,取自這裡。講述瞭如何提升UDP流的處理速率,但實際涉及的技術點不僅僅限於UDP。這篇文章中涉及的技術正好可以把前段時間瞭解的知識串聯起來。作者:Toshiaki Makita
講述內容
- 背景
- 提升網路效能的基本技術
- 如何提升UDP效能
作者介紹
Toshiaki Makita
- NTT開源軟體中心的Linux核心工程師
- NTT集團公司的技術支援
- 核心網路子系統的活躍補丁提交者
背景
因特網上UDP事務
- 使用UDP的服務
- DNS
- RADIUS
- NTP
- SNMP等
- 被大量網路服務提供商使用
乙太網頻寬和互動
- 乙太網頻寬演進:
- 10M -> 100M -> 1G -> 10G -> 40G -> 100G -> ...
- 10G(或更大)的NIC在商用伺服器上越來越普遍
- 10G網路上的互動:
- 最小報文的場景下:最大 14,880,952 個報文/s (最小的以太幀為64位元組+ preamble+IFG 20bytes = 84 bytes = 672 bits,10,000,000,000 / 672 = 14,880,952)
- 難以在單個伺服器中處理
需要處理多少互動
- UDP 負載大小
- DNS
- A/AAAA請求:40~位元組
- A/AAAA響應:100~位元組
- RADIUS
- Access-Request:70~位元組
- Access-Accept:30~位元組
- 通常帶有100個位元組的屬性
- 大部分場景下為100個位元組
- DNS
- 10G網路上100位元組資料的互動
- 最大7,530,120次互動/s (100 bytes + IP/UDP/Ether headers 46bytes + preamble+IFG 20bytes = 166 bytes = 1328 bits
,即10,000,000,000 / 1328 = 7,530,120) - 即使在少於最短的報文的情況下,但仍具有挑戰性
- 最大7,530,120次互動/s (100 bytes + IP/UDP/Ether headers 46bytes + preamble+IFG 20bytes = 166 bytes = 1328 bits
提升網路效能的基本技術
TSO/GSO/GRO
-
報文分割/聚合
-
減少報文在服務中的處理
-
適用於TCP 位元組流(使用UDP隧道的TCP也可以)
-
不適用於UDP資料包(除了UFO,其他都依賴物理NICs)
- UDP在資料包之間有明確的界限
- 不能分割/聚合報文
TSO/GSO用於傳送報文時,將上層聚合的資料進行分割,分割為不大於MTU的報文;GRO在接受側,將多個報文聚合為一個資料,上送給協議棧。總之就是將報文的處理下移到了網路卡上,減少了網路棧的負擔。TSO/GSO等可以增加網路吞吐量,但有可能造成某些連線上的網路延遲。
RSS
- 在多核伺服器上擴充套件了網路接收側的處理
- RSS本身是一個NIC特性
- 將報文分發到一個NIC中的多個佇列上
- 每個佇列都有一個不同的中斷向量(不同佇列的報文可以被不同的核處理)
- 可以運用於TCP/UDP
- 通常10G的NICs會支援RSS
RSS是物理網路卡支援的特性,可以將NIC的多個佇列對映到多個CPU核上進行處理,增加處理的效率,減少CPU中斷競爭。
啟用RSS的NIC的效能
- 100位元組UDP互動效能
- 使用簡單的echo多執行緒(執行緒數與核數相同,每個執行緒執行
recvfrom()
和sendto()
伺服器進行測試 - OS:核心4.6.3(RHEL 7.2環境)
- 具有20個核心和10G NIC的中型商用伺服器:
- NIC:Intel 82599ES (含RSS, 最大64 個佇列)
- CPU:Xeon E5-2650 v3 (2.3 GHz 10 cores) * 2 sockets,禁用超執行緒
- 結果:270,000 transactions/s (tps) (大概 360Mbps)
- 10G頻寬使用了3.6%
- 使用簡單的echo多執行緒(執行緒數與核數相同,每個執行緒執行
如何提升
確認瓶頸
-
sar -u ALL -P ALL 1
-
softirq僅在NUMA的Node0上執行,為什麼?
- 儘管可以為20個核提供足夠(64個)的佇列
可以在/proc/zoneinfo中檢視NUMA的node資訊。使用mpstat也可以看到類似的現象,
%irq
表示硬中斷,%soft
表示軟中斷。
RSS下的softirq
-
RSS會將報文分發到接收佇列
-
每個佇列的中斷目的地由
/proc/irq/<irq>/smp_affinity
確定RSS會將報文分發到不同的佇列,smp_affinity會設定中斷親和性,將不同佇列產生的中斷上送給不同的CPU核。
-
通常由
irqbalance
設定smp_affinity
校驗smp_affinity
-
smp_affinity
-
irqbalance
僅使用了Node 0(核0-4, 10-14),如何修改?
檢查affinity_hint
-
一些NIC驅動提供了
affinity_hint
-
affinity_hint
是均勻分佈的 -
為了顯示該hint,可以在
irqbalance
(通過/etc/sysconfig/irqbalance)中新增"-h exact"選項
修改irqbalance選項
-
新增"-h exact"並重啟
irqbalance
服務 -
可以看到irqs分佈到了所有的核上。
-
sar -u ALL -P ALL 1
-
雖然irqs看起來分佈均勻,但16~19核卻沒有分配softirq
檢查rx-queue狀態
-
ethtool -S
$ ethtool -S ens1f0 | grep 'rx_queue_.*_packets' rx_queue_0_packets: 198005155 rx_queue_1_packets: 153339750 rx_queue_2_packets: 162870095 rx_queue_3_packets: 172303801 rx_queue_4_packets: 153728776 rx_queue_5_packets: 158138563 rx_queue_6_packets: 164411653 rx_queue_7_packets: 165924489 rx_queue_8_packets: 176545406 rx_queue_9_packets: 165340188 rx_queue_10_packets: 150279834 rx_queue_11_packets: 150983782 rx_queue_12_packets: 157623687 rx_queue_13_packets: 150743910 rx_queue_14_packets: 158634344 rx_queue_15_packets: 158497890 rx_queue_16_packets: 4 rx_queue_17_packets: 3 rx_queue_18_packets: 0 rx_queue_19_packets: 8
-
可以看到RSS並沒有將報文分發給佇列16~19
RSS 間接表
-
RSS有一個間接表,用於確定分發的報文所屬的佇列
-
可以使用
ethtool -x
命令檢視(虛擬環境可能不支援) -
可以看到僅使用了015的接收佇列,並沒有使用1619的佇列
-
使用所有的0~19的佇列
# ethtool -X ens1f0 equal 20 Cannot set RX flow hash configuration: Invalid argument
-
間接表中該NIC的最大接收佇列數為16,因此不能使用20個佇列
- 雖然有64個接收佇列
-
使用RPS替代
- RSS的軟體模擬
使用RPS
-
現在給接收佇列69上的流分配CPU69 和16~19,這兩組CPU都位於Node1
• rx-queue 6 -> core 6, 16
• rx-queue 7 -> core 7, 17
• rx-queue 8 -> core 8, 18
• rx-queue 9 -> core 9, 19
# echo 10040 > /sys/class/net/ens1f0/queues/rx-6/rps_cpus
# echo 20080 > /sys/class/net/ens1f0/queues/rx-7/rps_cpus
# echo 40100 > /sys/class/net/ens1f0/queues/rx-8/rps_cpus
# echo 80200 > /sys/class/net/ens1f0/queues/rx-9/rps_cpus
-
sar -u ALL -P ALL 1
-
此時軟中斷的分佈幾乎是均勻的
RSS & affinity_hint & RPS
-
多虧了affinity_hint 和RPS,現在可以將流均勻地分發到不同的CPU核上。
-
效能變化:
- Before: 270,000 tps (大概 360Mbps)
- After: 17,000 tps (大概 23Mbps)
變的更差了。。
-
可能的原因是軟中斷太多導致的
- 軟中斷幾乎佔了100%的CPU
- 需要更好地分析手段
分析軟中斷
-
perf
- 核心樹分析工具
- 通過CPU取樣定位熱點
-
舉例:
perf record -a -g -- sleep 5
- 每5秒將結果輸出到
perf.data
檔案
- 每5秒將結果輸出到
-
火焰圖
- 以svg格式視覺化展現
perf.data
- https://github.com/brendangregg/FlameGraph
- 以svg格式視覺化展現
-
CPU0的火焰圖(結果經過了過濾)
- X軸:CPU消耗
- Y軸:呼叫深度
-
queued_spin_lock_slowpath
:鎖競爭 -
udp_queue_rcv_skb
:要求socket鎖
socket鎖競爭
-
echo伺服器在一個特定埠上僅繫結了一個socket
-
每個核心的softirq同時將報文推入socket佇列
-
最終導致socket鎖競爭
避免鎖競爭
- 使用
SO_REUSEPORT
選項分割sockets- 該選項在核心3.9引入,預設使用流(報文首部)雜湊來選擇socket
-
SO_REUSEPORT
允許多個UDP socket繫結到相同的埠上-
在每個報文排隊時選擇一個套接字
int on = 1; int sock = socket(AF_INET, SOCK_DGRAM, 0); setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)); bind(sock, ...);
SO_REUSEPORT
的介紹可以參考這篇文章。
-
使用SO_REUSEPORT
-
sar -u ALL -P ALL 1
-
此時軟中斷消耗的CPU就比較合理了,從下面火焰圖可以看到中斷處理消耗的CPU縮短了
-
效能變化
- RSS: 270,000 tps (大概 360Mbps)
- +affinity_hint+RPS: 17,000 tps (大概 23Mbps)
- +SO_REUSEPORT: 2,540,000 tps (大概 3370Mbps)
-
進一步分析:
-
可以看到,仍然有socket鎖競爭。
SO_REUSEPORT
預設使用流雜湊來選擇佇列,不同的CPU核可能會選擇相同的sockets,導致競爭。
避免socket鎖競爭
- 根據CPU核號選擇socket
- 通過
SO_ATTACH_REUSEPORT_CBPF/EBPF
實現 - 在核心4.5引入上述功能
- 通過
-
此時軟中斷之間不再產生競爭
-
用法可以參見核心原始碼樹中的例子:tools /testing/selftests /net /reuseport_bpf_cpu.c
-
啟用
SO_ATTACH_REUSEPORT_EPBF
前後的火焰圖如下,可以看到中斷消耗的CPU更少了 -
效能變化:
- RSS: 270,000 tps (approx. 360Mbps)
- +affinity_hint+RPS: 17,000 tps (大概 23Mbps)
- +SO_REUSEPORT: 2,540,000 tps (大概 3370Mbps)
- +SO_ATTACH_...: 4,250,000 tps (大概 5640Mbps)
固定使用者執行緒
-
使用者執行緒數:sockets數 == 1:1,但不一定與軟中斷處於同一CPU核
-
將使用者現場固定到相同的核,獲得更好的快取親和性。可以使用cgroup, taskset, pthread_setaffinity_np()等方式
-
效能變化
- RSS: 270,000 tps (approx. 360Mbps)
- +affinity_hint+RPS: 17,000 tps (大概 23Mbps)
- +SO_REUSEPORT: 2,540,000 tps (大概 3370Mbps)
- +SO_ATTACH_...: 4,250,000 tps (大概 5640Mbps)
- +Pin threads: 5,050,000 tps (大概 6710Mbps)
輸出方向的鎖
- 到目前為止解決的問題都處在接收方向上
- 傳送方向是否有鎖競爭?
Tx佇列
- 核心具有Qdisc(預設的Qdisc為
pfifo_fast
) - 每個Qdisc都連線到NIC的tx佇列
- 每個Qdisc都有自己的鎖
Tx佇列的鎖競爭
- Qdisc預設通過流雜湊進行選擇
- 因此可能會傳送鎖競爭
- 但並沒有在輸出方向上看到鎖競爭,為什麼?
避免Tx佇列的鎖競爭
- 這是因為
ixgbe
(Intel 10GbE NIC驅動)可以自動設定XPS
-
XPS允許核心選擇根據CPU核號選擇Tx佇列(Qdisc)
-
因此傳送方向沒有鎖競爭
XPS的影響如何
-
禁用XPS
# for ((txq=0; txq<20; txq++)); do > echo 0 > /sys/class/net/ens1f0/queues/tx-$txq/xps_cpus > done
- Before: 5,050,000 tps (大概 6710Mbps)
- After: 1,086,000 tps (大概 1440Mbps)
可見有近5倍的效能差距,且從火焰圖看,產生了大量鎖競爭
重新啟用XPS
-
啟動XPS。XPS的工作方式其實與RPS類似
# echo 00001 > /sys/class/net/<NIC>/queues/tx-0/xps_cpus # echo 00002 > /sys/class/net/<NIC>/queues/tx-1/xps_cpus # echo 00004 > /sys/class/net/<NIC>/queues/tx-2/xps_cpus # echo 00008 > /sys/class/net/<NIC>/queues/tx-3/xps_cpus ...
-
儘管
ixgbe
可以自動設定XPS,但並不是所有的驅動都可以 -
確保配置了xps_cpus
優化單個核 1
- 為了完全利用多核,並避免競爭,效能達到了5,050,000 tps (大概 6710Mbps)
- 為了進一步提高效能,需要降低單個核的開銷
-
可以看到預設啟用了GRO,並消耗了4.9%的CPU時間。
-
GRO並不適用於UDP(UDP隧道除外,如VXLAN)
-
為UDP服務禁用GRO
# ethtool -K <NIC> gro off
-
警告:
- 如果關注TCP效能,則不能禁用GRO功能
- 禁用GRO會導致TCP接收吞吐量降低
- 在KVM 虛擬化管理系統上也不要禁用GRO
- GRO提高了隧道協議流量以及虛擬機器管理程式上的guest TCP流量的吞吐量
- 如果關注TCP效能,則不能禁用GRO功能
禁用GRO
- 效能變化
• RSS (+XPS): 270,000 tps (大概 360Mbps)
• +affinity_hint+RPS: 17,000 tps (大概 23Mbps)
• +SO_REUSEPORT: 2,540,000 tps (大概 3370Mbps)
• +SO_ATTACH_...: 4,250,000 tps (大概 5640Mbps)
• +Pin threads: 5,050,000 tps (大概 6710Mbps)
• +Disable GRO: 5,180,000 tps (大概 6880Mbps)
優化單個核 2
-
可以看到執行了iptables相關的操作
- 此時並不需要任何iptables
-
iptables消耗了3%的CPU
-
由於iptables是核心載入的模組,即使使用者不需要任何規則,它內部也會消耗一部分CPU
-
即使不新增任何規則,某些發行版也會載入iptables模組
-
如果不需要iptables,則解除安裝該模組
# modprobe -r iptable_filter # modprobe -r ip_tables
-
效能變化
• RSS (+XPS): 270,000 tps (大概 360Mbps)
• +affinity_hint+RPS: 17,000 tps (大概 23Mbps)
• +SO_REUSEPORT: 2,540,000 tps (大概 3370Mbps)
• +SO_ATTACH_...: 4,250,000 tps (大概 5640Mbps)
• +Pin threads: 5,050,000 tps (大概 6710Mbps)
• +Disable GRO: 5,180,000 tps (大概 6880Mbps)
• +Unload iptables: 5,380,000 tps (大概 7140Mbps)
優化單個核3
-
在接收路徑上,FIB查詢了兩次。每次消耗1.82%~的CPU時間。其中以此用於校驗源IP地址:
- 反向路徑過濾器
- 本地地址校驗
-
如果不需要源校驗,則可以忽略
# sysctl -w net.ipv4.conf.all.rp_filter=0 # sysctl -w net.ipv4.conf.<NIC>.rp_filter=0 # sysctl -w net.ipv4.conf.all.accept_local=1
-
效能變化
• RSS (+XPS): 270,000 tps (大概 360Mbps)
• +affinity_hint+RPS: 17,000 tps (大概 23Mbps)
• +SO_REUSEPORT: 2,540,000 tps (大概 3370Mbps)
• +SO_ATTACH_...: 4,250,000 tps (大概 5640Mbps)
• +Pin threads: 5,050,000 tps (大概 6710Mbps)
• +Disable GRO: 5,180,000 tps (大概 6880Mbps)
• +Unload iptables: 5,380,000 tps (大概 7140Mbps)
• +Disable validation: 5,490,000 tps (大概 7290Mbps)
優化單個核4
-
當大量處理報文時,Audit消耗的CPU會變大。大概消耗2.5%的CPU時間
-
如果不需要audit,則禁用
# systemctl disable auditd # reboot
-
效能變化
• RSS (+XPS): 270,000 tps (大概 360Mbps)
• +affinity_hint+RPS: 17,000 tps (大概 23Mbps)
• +SO_REUSEPORT: 2,540,000 tps (大概 3370Mbps)
• +SO_ATTACH_...: 4,250,000 tps (大概 5640Mbps)
• +Pin threads: 5,050,000 tps (大概 6710Mbps)
• +Disable GRO: 5,180,000 tps (大概 6880Mbps)
• +Unload iptables: 5,380,000 tps (大概 7140Mbps)
• +Disable validation: 5,490,000 tps (大概 7290Mbps)
• +Disable audit: 5,860,000 tps (大概 7780Mbps)
優化單個核5
-
IP ID欄位計算(__ip_select_ident)消耗的CPU比較多,消耗大概4.82%的CPU
-
該欄位用於解決特定環境下的問題
- 如果很多客戶端使用了相同的IP地址
- 原子操作會造成快取競爭
- 如果不使用隧道協議,很可能看不到如此大量的CPU消耗
- 如果很多客戶端使用了相同的IP地址
-
如果真的碰到這種問題
-
只有在不傳送大於MTU的報文時才可以跳過它
-
雖然非常嚴格
int pmtu = IP_PMTUDISC_DO; setsockopt(sock, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu));
-
-
-
效能變化
• RSS (+XPS): 270,000 tps (大概 360Mbps)
• +affinity_hint+RPS: 17,000 tps (大概 23Mbps)
• +SO_REUSEPORT: 2,540,000 tps (大概 3370Mbps)
• +SO_ATTACH_...: 4,250,000 tps (大概 5640Mbps)
• +Pin threads: 5,050,000 tps (大概 6710Mbps)
• +Disable GRO: 5,180,000 tps (大概 6880Mbps)
• +Unload iptables: 5,380,000 tps (大概 7140Mbps)
• +Disable validation: 5,490,000 tps (大概 7290Mbps)
• +Disable audit: 5,860,000 tps (大概 7780Mbps)
• +Skip ID calculation: 6,010,000 tps (大概 7980Mbps)
超執行緒
-
目前還沒有啟用超執行緒
-
啟用之後的邏輯核為40個
- 物理核為20個
-
需要給40個核配置RPS
- 提示:最大可用的接收佇列為16
-
啟用超執行緒,並在所有的接收佇列上設定RPS
• queue 0 -> core 0, 20
• queue 1 -> core 1, 21
• ...
• queue 10 -> core 10, 16, 30
• queue 11 -> core 11, 17, 31
• ... -
效能變化
• RSS (+XPS): 270,000 tps (大概 360Mbps)
• +affinity_hint+RPS: 17,000 tps (大概 23Mbps)
• +SO_REUSEPORT: 2,540,000 tps (大概 3370Mbps)
• +SO_ATTACH_...: 4,250,000 tps (大概 5640Mbps)
• +Pin threads: 5,050,000 tps (大概 6710Mbps)
• +Disable GRO: 5,180,000 tps (大概 6880Mbps)
• +Unload iptables: 5,380,000 tps (大概 7140Mbps)
• +Disable validation: 5,490,000 tps (大概 7290Mbps)
• +Disable audit: 5,860,000 tps (大概 7780Mbps)
• +Skip ID calculation: 6,010,000 tps (大概 7980Mbps)
• +Hyper threading: 7,010,000 tps (大概 9310Mbps) -
猜測,如果更多的rx佇列可能會獲得更好的效能
更多熱點1
- Tx Qdisc鎖(_raw_spin_lock)的消耗比較嚴重
- 沒有競爭,但出現了很多原子操作
- 在Linux netdev社群中進行優化
更多熱點2
- slab記憶體申請和釋放
- 在Linux netdev社群中進行優化
其他挑戰
- UDP伺服器的環境為guest
- Hypervisor可能使CPU飽和或丟棄報文
總結
-
對於100位元組的資料,可以達到幾乎10G的速率
- 從:270,000 tps (approx. 360Mbps)
- 到:7,010,000 tps (approx. 9310Mbps)
-
提高UDP效能
-
應用(最關鍵)
- 實現
SO_REUSEPORT
- 實現
SO_ATTACH_REUSEPORT_EBPF/CBPF
- 對TCP監聽socket同樣有效
- 實現
-
OS設定
-
檢查smp_affinity
-
如果rx佇列不足,則使用RPS
-
確保配置了XPS
-
考慮如下降低單核開銷的方法
• Disable GRO
• Unload iptables
• Disable source IP validation
• Disable auditd
-
-
-
硬體
- 如果可能,使用具有足夠RSS接收佇列的NICs(如核數相同的佇列)