圖解 TCP 收發包需要注意的內核參數
背景
TCP 收包和發包的過程是網絡應用中容易出現問題的地方。收包是指數據到達網卡並被應用程序開始處理的過程;發包則是應用程序調用發包函數到數據包從網卡發出的過程。常見的問題包括:
-
網卡中斷過多,佔用大量 CPU,影響業務性能;
-
應用程序調用
write()
或send()
發送數據包卻未成功; -
數據包已被網卡接收但應用程序未收到;
-
調整緩衝區大小無效;
-
內核緩衝區可能已滿導致丟包,如何觀察?
要解決這些問題,我們需要理解 TCP 收發包過程中的關鍵因素,以及如何配置參數使之與業務場景匹配。下面我們來分析 TCP 數據包的發送和接收過程。
TCP 數據包的發送過程
摘自:https://time.geekbang.org/column/article/285816
在應用程序調用 write(2)
或 send(2)
系列系統調用開始發包時,數據包會從用戶緩衝區複製到 TCP 發送緩衝區(TCP Send Buffer)。緩衝區大小默認由 net.ipv4.tcp_wmem
控制:
sysctl net.ipv4.tcp_wmem
net.ipv4.tcp_wmem = 4096 87380 16777216
• min: 最小緩衝區大小
• default: 初始緩衝區大小
• max: 最大緩衝區大小
緩衝區大小在 min 和 max 間動態調 整,初始大小爲 default
。tcp_wmem
中的 max
不能超過 net.core.wmem_max
的值:
sysctl net.core.wmem_max
net.core.wmem_max = 16777216
固定緩衝區設置
有時應用程序會明確指定發送數據大小,可通過 setsockopt(2)
的 SO_SNDBUF
設置固定的緩衝區大小,此時 tcp_wmem
失效,緩衝區大小不再動態調整。SO_SNDBUF
的值也不能超過 net.core.wmem_max
。
TCP 連接總內存限制
系統中可能有大量 TCP 連接,總內存使用受 net.ipv4.tcp_mem
控制:
sysctl net.ipv4.tcp_mem
net.ipv4.tcp_mem = 94500000 915000000 927000000
• min: 最小值
• pressure: 內存壓力閾值
• max: 最大內存
達到 max 限制會影響數據發送。若 tcp_mem 限制觸發,可以使用以下命令觀察:
# 檢查系統是否支持 tracing,如果不支持需要先開啓,這裏不展開說明
ls /sys/kernel/debug/tracing
# 啓用 sock_exceed_buf_limit 事件追蹤
echo 1 > /sys/kernel/debug/tracing/events/sock/sock_exceed_buf_limit/enable
# 監控事件日誌
cat /sys/kernel/debug/tracing/trace_pipe
# 例如
tcp_sendmsg: pid=3215 comm=nginx send buf exceed limit: sk=000000007b3ed4f0, pid=12345
# 分析結果並調整參數,例如
sysctl -w net.ipv4.tcp_wmem="4096 87380 16777216"
sysctl -w net.core.wmem_max=16777216
# 繼續觀察,確保沒有想過事件
# 停止事件追蹤
echo 0 > /sys/kernel/debug/tracing/events/sock/sock_exceed_buf_limit/enable
# 清理事件
echo > /sys/kernel/debug/tracing/trace
IP 層配置項
在 IP 層,net.ipv4.ip_local_port_range
控制本地端口範圍。若默認範圍過小,可能導致連接創建失敗:
sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 1024 65535
qdisc
是 Linux 內核的流控實現,txqueuelen
是 qdisc
隊列長度。隊列太小可能導致丟包,可使用以下命令檢查:
ip -s -s link ls dev eno1
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 3c:ec:ef:4e:15:06 brd ff:ff:ff:ff:ff:ff
RX: bytes packets errors dropped overrun mcast
515151229849 1290891028 0 0 0 3029910
RX errors: length crc frame fifo missed
0 0 0 0 0
TX: bytes packets errors dropped carrier collsns
397751986726 1242924130 0 0 0 0
TX errors: aborted fifo window heartbeat transns
0 0 0 0 4
若 dropped
項不爲 0,可能需增大 txqueuelen
:
ifconfig eth0 txqueuelen 2000
默認的 qdisc
爲 pfifo_fast
,通常無需調整;若使用 TCP BBR
擁塞控制,需將其設置爲 fq
:
sysctl net.core.default_qdisc=fq
TCP 數據包的接收過程
摘自:https://time.geekbang.org/column/article/285816
數據包到達網卡後會觸發中斷(IRQ)通知 CPU 讀取數據包。高性能網絡場景中數據包量大,頻繁中斷影響性能,NAPI
機制通過輪詢(poll)批量處理數據包。輪詢數量受 net.core.netdev_budget
控制:
sysctl net.core.netdev_budget
net.core.netdev_budget = 300
可根據吞吐量需求適當調大此值,例如增至 600。調大此值會增加 CPU 輪詢時間,可能增加其他任務的調度延遲。
TCP 接收緩衝區
TCP 接收緩衝區大小由 net.ipv4.tcp_rmem
控制:
sysctl net.ipv4.tcp_rmem
net.ipv4.tcp_rmem = 4096 87380 16777216
• min: 最小緩衝區大小
• default: 初始緩衝區大小
• max: 最大緩衝區大小
默認情況下,接收緩衝區大小動態調節。tcp_moderate_rcvbuf
控制動態調節開關,通常保持打開(值爲 1):
sysctl net.ipv4.tcp_moderate_rcvbuf
net.ipv4.tcp_moderate_rcvbuf = 1
應用程序也可通過 SO_RCVBUF
設置固定的接收緩衝區大小。類似發送緩衝區,SO_RCVBUF
的值不能超過 net.core.rmem_max
:
sysctl net.core.rmem_max
net.core.rmem_max = 16777216
注意:只有在 tcp_moderate_rcvbuf
爲 1,並且應用程序沒有通過 SO_RCVBUF
來配置緩衝區大小的情況下,TCP 接收緩衝區纔會動態調節。
BPF 工具觀測
《BPF 性能之巔》一書第四章提到的 sormem
工具,用於跟蹤套接字接收隊列的大小,以直方圖的形式顯示與可調限制相比,接收隊列的滿載程度。如果接收隊列超出限制,數據包將被丟棄,從而導致性能問題。
/bpf-perf-tools-book-master/originals/Ch10_Networking# ./sormem.bt
Attaching 4 probes...
Tracing socket receive buffer size. Hit Ctrl-C to end.
^C
@rmem_alloc:
[0] 2 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[1] 0 | |
[2, 4) 0 | |
[4, 8) 0 | |
[8, 16) 0 | |
[16, 32) 0 | |
[32, 64) 0 | |
[64, 128) 0 | |
[128, 256) 0 | |
[256, 512) 0 | |
[512, 1K) 0 | |
[1K, 2K) 0 | |
[2K, 4K) 2 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
@rmem_limit:
[128K, 256K) 4 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
@rmem_alloc
顯示爲接收緩衝區分配了多少內存。@rmem_limit
是接收緩衝區的限制大小,使用 net.ipv4.tcp_rmem
進行調整。
關於 bpftrace 的使用可以參考:《BPF 性能之巔》讀書筆記 - bpftrace 入門。書籍可以通過如下方式購買:
傳統工具 - ss 觀測
命令行執行:
ss -tiepm4 | head
Failed to open cgroup2 by ID
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ESTAB 0 0 192.168.103.35:48682 192.168.103.36:12005 users:(("mysqld",pid=1068110,fd=182)) ino:2779900121 sk:548dc90 cgroup:/machine.slice/docker.service <->
skmem:(r0,rb6291456,t0,tb3497472,f4096,w0,o0,bl0,d78) ts sack cubic wscale:7,7 rto:220 rtt:18.805/20.763 ato:48 mss:4040 pmtu:4092 rcvmss:140 advmss:4040 cwnd:10 ssthresh:367 bytes_sent:21203293400 bytes_retrans:17832 bytes_acked:21203275569 bytes_received:1347875756 segs_out:21903559 segs_in:20225116 data_segs_out:21277948 data_segs_in:9598986 send 17.2Mbps lastsnd:188 lastrcv:188 lastack:148 pacing_rate 34.4Mbps delivery_rate 2.69Gbps delivered:21277949 app_limited busy:86620844ms rwnd_limited:4ms(0.0%) retrans:0/53 dsack_dups:53 rcv_rtt:1 rcv_space:282380 rcv_ssthresh:1841728 minrtt:0.016
結果解析:
重點說明:
-
LISTEN 狀態:
Recv-Q
表示當前listen backlog
隊列(全連接隊列:等待用戶調用 accept() 獲取的、已完成 3 次握手的 socket 連接隊列)中的連接數量,而Send-Q
表示了listen socket
最大能容納的backlog
,即min(backlog, somaxconn)
值。 -
非 LISTEN 狀態:
Recv-Q
表示了receive queue
中存在的字節數;Send-Q
表示send queue
中存在的字節數。
總結
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/3pGiiWPLHQsVNzTjBy6lmg