先來回顧下三次握手里面涉及到的問題:
1. 當 client 通過 connect 向 server 發出 SYN 包時,client 會維護一個 socket 等待隊列,而 server 會維護一個 SYN 隊列
2. 此時進入半鏈接的狀態,如果 socket 等待隊列滿了,server 則會丟棄,而 client 也會由此返回 connection time out;只要是 client 沒有收到 SYN+ACK,3s 之后,client 會再次發送,如果依然沒有收到,9s 之后會繼續發送
3. 半連接 syn 隊列的長度為 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog) 決定
4. 當 server 收到 client 的 SYN 包后,會返回 SYN, ACK 的包加以確認,client 的 TCP 協議棧會喚醒 socket 等待隊列,發出 connect 調用
5. client 返回 ACK 的包后,server 會進入一個新的叫 accept 的隊列,該隊列的長度為 min(backlog, somaxconn),默認情況下,somaxconn 的值為 128,表示最多有 129 的 ESTAB 的連接等待 accept(),而 backlog 的值則由 int listen(int sockfd, int backlog) 中的第二個參數指定,listen 里面的 backlog 的含義請看這里。需要注意的是,一些 Linux 的發型版本可能存在對 somaxcon 錯誤 truncating 方式。
6. 當 accept 隊列滿了之后,即使 client 繼續向 server 發送 ACK 的包,也會不被相應,此時,server 通過 /proc/sys/net/ipv4/tcp_abort_on_overflow 來決定如何返回,0 表示直接丟丟棄該 ACK,1 表示發送 RST 通知 client;相應的,client 則會分別返回 read timeout 或者 connection reset by peer。上面說的只是些理論,如果服務器不及時的調用 accept(),當 queue 滿了之后,服務器並不會按照理論所述,不再對 SYN 進行應答,返回 ETIMEDOUT。根據這篇文檔的描述,實際情況並非如此,服務器會隨機的忽略收到的 SYN,建立起來的連接數可以無限的增加,只不過客戶端會遇到延時以及超時的情況。
可以看到,整個 TCP stack 有如下的兩個 queue:
1. 一個是 half open(syn queue) queue(max(tcp_max_syn_backlog, 64)),用來保存 SYN_SENT 以及 SYN_RECV 的信息。
2. 另外一個是 accept queue(min(somaxconn, backlog)),保存 ESTAB 的狀態,但是調用 accept()。
注意,之前我對 Recv-Q/Send-Q 的理解有些誤差,使用 ss 獲取到的 Recv-Q/Send-Q 在 LISTEN 狀態以及非 LISTEN 狀態所表達的含義是不同的。從 tcp_diag.c 源碼中可以看到二者的區別:
LISTEN 狀態: Recv-Q 表示的當前等待服務端調用 accept 完成三次握手的 listen backlog 數值,也就是說,當客戶端通過 connect() 去連接正在 listen() 的服務端時,這些連接會一直處於這個 queue 里面直到被服務端 accept();Send-Q 表示的則是最大的 listen backlog 數值,這就就是上面提到的 min(backlog, somaxconn) 的值。
其余狀態: 非 LISTEN 狀態之前理解的沒有問題。Recv-Q 表示 receive queue 中的 bytes 數量;Send-Q 表示 send queue 中的 bytes 數值。
通過 "SYNs to LISTEN sockets dropped" 以及 "times the listen queue of a socket overflowed" 這兩個 netstat -s 獲取到的 TCP 狀態,可以很快的發現系統存在的一些問題。
任何一個包含 "dropped" 或者 "overflowed" 並且數值一直居高不下的 metric 從字面含義理解來看,都不是一個好現象。
對於 Nginx 來說,backlog 的默認值為 511,這個可以通過 ss/netstat 的 Send-Q 確認:
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 511 *:80 *:*
可以通過適當的增大 nginx 的 backlog 以及 somaxconn 來增大隊列:
listen 80 backlog=1638
上面說了這么多,其實就是為了引入下面這個問題。
我們線上一個基於 Netty 的代碼,3.5.12 的版本,監控顯示 "times the listen queue of a socket overflowed" 常年居高不下,動輒幾十 K,通過 ss,我們發現其 backlog 的值只有 50:
Recv-Q Send-Q Local Address:Port Peer Address:Port
0 50 *:6928 *:* users:(("java",454409,196))
g 了一下,發現這個版本復用了 Java 默認的 50 這個值。將其增加到 1024 測試,監控曲線一下子降低到了 0。
除了上面這些,還有一個比較基礎的 net.core.netdev_max_backlog,如果內核接受包的速度大於被 userspace 處理的速度,該值定義了可以在接口輸入最大的的包數量。
chartbeat 分享了兩篇很精彩的文檔,其中涉及到了 queue 的一些問題。
Lessons learned tuning TCP and Nginx in EC2 1
Lessons learned tuning TCP and Nginx in EC2 2
ref:
http://madalanarayana.wordpress.com/2014/04/13/learnings-on-tcp-syn/
轉自:http://jaseywang.me/2014/07/20/tcp-queue-的一些問題/
