前言

UDP 若發生丟包,往往會認為封包遺失在路上,然而需要因環境架構來進行判斷

若發生問題的在一個大量的高速 UDP 的環境,則狀況可能會更加複雜,但往往事實出乎意料

知識

接收

網卡接收封包存入 Ring Buffer 然後透過 Kernel 存入 Socket Buffer 然後 Application 再從 Socket Buffer 取出封包

傳出

APP 將封包透過系統調用執行 sendTo 經歷 UDP封裝、IP封裝後進入 Socket Buffer,然後經由 Traffic Control 的 Qdisc(Classless Queuing Disciplines),而 Qdisc 會有一套傳輸的規則,符合規則才從 Ring Buffer 透過網卡送出

發生原因

檢查流程

確認網路協議統計 UDP 是否出現丟包

buffer errors 是否不為 0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ netstat -su
Udp:
    1279567461 packets received
    137012 packets to unknown port received
    4 packet receive errors
    1319924023 packets sent
    0 receive buffer errors
    161797914 send buffer errors
    InCsumErrors: 4
    IgnoredMulti: 2431441

確認網路協議是否丟包

bufErrors 是否不為 0

1
$ grep -E "^Udp:|^Ip:" /proc/net/snmp | column -t

會顯下如下數值

IP UDP
Forwarding 2 InDatagrams 1279567583
DefaultTTL 64 NoPorts 137018
InReceives 1326807907 InErrors 4
InHdrErrors 4 OutDatagrams 1322634707
InAddrErrors 2710 RcvbufErrors 0
ForwDatagrams 0 SndbufErrors 161837953
InUnknownProtos 0 InCsumErrors 4
InDiscards 0 IgnoredMulti 2439269
InDelivers 1326738388
OutRequests 1524661777
OutDiscards 161838402
OutNoRoutes 595
ReasmTimeout 0
ReasmReqds 0
ReasmOKs 0
ReasmFails 0
FragOKs 0
FragFails 0
FragCreates 0

確認網卡傳出是否丟包

1
2
3
$ ifocnifg -l eth1
Iface  MTU   RX-OK    RX-ERR RX-DRP RX-OVR  TX-OK    TX-ERR TX-DRP TX-OVR Flg
eth1   1500  83631360 0      30959  0       42005447 0      0      0      BMRU

確認流量控制是否出現丟包

Sent dropped 是否不為 0

1
2
3
4
5
6
7
8
9
$ tc -s qdisc dev eth1
qdisc mq 0: root
 Sent 686625088192 bytes 1278323590 pkt (dropped 0, overlimits 0 requeues 462)
 backlog 0b 0p requeues 462
qdisc fq_codel 0: parent :1 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
 Sent 686625088192 bytes 1278323590 pkt (dropped 0, overlimits 0 requeues 462)
 backlog 0b 0p requeues 462
  maxpacket 550 drop_overlimit 0 new_flow_count 111116322 ecn_mark 0
  new_flows_len 0 old_flows_len 0

解決方法

網卡丟包

可以考慮增加網卡 TX/RX Buffer

調高 Buffer 會增加延遲,需平衡調整

需要無服無狀態時才能執行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 查看
$ ethtool -g eth1
Ring parameters for eno1:
Pre-set maximums:
RX:		2047
RX Mini:	n/a
RX Jumbo:	n/a
TX:		511
Current hardware settings:
RX:		200
RX Mini:	n/a
RX Jumbo:	n/a
TX:		300
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 增加 Buffer
$ ethtool -G eth1 tx 400

# 查看
$ ethtool -g eth1
Ring parameters for eno1:
Pre-set maximums:
RX:		2047
RX Mini:	n/a
RX Jumbo:	n/a
TX:		511
Current hardware settings:
RX:		200
RX Mini:	n/a
RX Jumbo:	n/a
TX:		400

調整 Kernel Socker Buffer

確認需求才進行調整

TimeSlice 是 100ms / 0.001s

接收

假設需求是可以接收 5000 TPS、封包大小約 800 Bytes

理想狀態如下

0.001 秒需要接收 5 個封包,也能如其接收 5 個封包,則使用預設的 net.core.rmem_maxnet.core.rmem_default 即可

假如狀態如下

0.001 秒接收 20 個封包,而效能只能接收 5 個封包,暫時多出了 15 個封包,會先暫時留在 RX Buffer 裡面

累積持續一秒的話會有 15000 個封包留在 Buffer 裡面,約 12MB 的使用量 如果要能承受這一秒,我個人會建議設定成 13631488 (13MB)

1
2
3
$ echo "net.core.rmem_max=13631488" >> /etc/sysctl.conf
$ echo "net.core.rmem_default=13631488" >> /etc/sysctl.conf
$ sysctl -p

傳出

假設需求是要能傳出 5000 TPS、封包大小約 1000 Bytes

理想狀態如下

0.001 秒需要傳出 5 個封包,也能如其傳送 5 個封包,則使用預設的 net.core.wmem_maxnet.core.wmem_default 即可

假設狀態如下

0.001 秒傳出 20 個封包,而效能只能傳送出 5 個封包,暫時多出了 15 個封包,會先暫時留在 TX Buffer 裡面

累積持續一秒的話會有 15000 個封包留在 Buffer 裡面,約 15MB 的使用量 如果要能承受這一秒,我個人會建議設定成 16777216 (16MB)

1
2
3
$ echo "net.core.wmem_max=16777216" >> /etc/sysctl.conf
$ echo "net.core.wmem_default=16777216" >> /etc/sysctl.conf
$ sysctl -p

TC Qdisc 針對傳出丟包

需要因應不同的 Qdisc 規則來判斷該如何調整

每個規則會因應不同的狀況進行調整

pfifo_fast 只能調整 txqueuelen

1
$ ifocnifg eth1 txqueuelen <number>

fq_codel 需要因封包大小、反應時間等等狀況來判斷調整,在此簡單設定 interval * interval: 封包超出此時間未回應則丟棄

1
$ tc qdisc add dev eno1 root fq_codel limit 1000ms

fqfq_codel 一樣需要因封包大小、反應時間等等狀況來判斷調整,這邊單純加大 * limit: 硬體封包佇列限制,超過就丟包 * flow_limit: 硬體進入佇列的封包數量限制,超過就丟包

1
$ tc qdisc add dev eno1 root fq limit 20000 flow_limit 200

APP 處理太慢

各位開發人員盡力之

Ref

  1. SENDTO sends packet, but sniffer does not see it
  2. LINUX TRAFFIC CONTROL
  3. tc-pfifo_fast(8) — Linux manual page
  4. tc-fq_codel(8) — Linux manual page
  5. tc-fq(8) — Linux manual page