Tomcat 調優之從 Linux 內核源碼層面看 Tcp backlog( 二 )


listen backlog 參數其實就是我們調用 listen 函數時傳入的第二個參數 ?;氐街黝},Tomcat 的 accept-count 其實最后就會傳給 listen 函數做 backlog 用 。
int listen(int sockfd, int backlog);可以在配置文件中配置 tomcat accept-count 大小,默認為 100

Tomcat 調優之從 Linux 內核源碼層面看 Tcp backlog

文章插圖
以下代碼注釋中也注明了 acceptCount 就是 backlog
Tomcat 調優之從 Linux 內核源碼層面看 Tcp backlog

文章插圖
以 Nio2Endpoint 為例看下代碼,bind 方法首先會根據配置的核心線程數、最大線程數創建 worker 線程池 。然后調用 jdk nio2 中的 AsynchronousServerSocketChannelImpl 的 bind 方法 , 該方法內會調用 Net.listen() 進行 socket 監聽 。通過這幾段代碼,我們可以清晰的看到 Tomcat accept-count = Tcp backlog,默認值為 100 。
Tomcat 調優之從 Linux 內核源碼層面看 Tcp backlog

文章插圖

Tomcat 調優之從 Linux 內核源碼層面看 Tcp backlog

文章插圖

Tomcat 調優之從 Linux 內核源碼層面看 Tcp backlog

文章插圖
上面說到了半全兩個連接隊列,至于這兩個連接隊列大小怎么確定,其實不同 linux 內核版本算法也都不太一樣,我們就以 v3.10 來看 。
以下是 linux 內核 socket.c 中的源碼 , 也就是我們調用 listen() 函數會執行的代碼
/* * Perform a listen. Basically, we allow the protocol to do anything * necessary for a listen, and if that works, we mark the socket as * ready for listening. */SYSCALL_DEFINE2(listen, int, fd, int, backlog){struct socket *sock;int err, fput_needed;int somaxconn;sock = sockfd_lookup_light(fd, &err, &fput_needed);if (sock) {somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;if ((unsigned int)backlog > somaxconn)backlog = somaxconn;err = security_socket_listen(sock, backlog);if (!err)err = sock->ops->listen(sock, backlog);fput_light(sock->file, fput_needed);}return err;}可以看到,此處會拿內核參數 somaxconn 和 傳入的 backlog 做比較,取二者中的較小者作為全連接隊列大小 。
全連接隊列大小 = min(backlog, somaxconn) 。
接下來 backlog 會依次傳遞給如下函數,格式約定(源代碼文件名#函數名)
af_inet.c#inet_listen() -> inet_connection_sock.c#inet_csk_listen_start() -> request_sock.c#reqsk_queue_alloc()
reqsk_queue_alloc() 函數代碼如下,主要就是用來計算半連接隊列大小的 。
Tomcat 調優之從 Linux 內核源碼層面看 Tcp backlog

文章插圖
計算邏輯可以簡化為下述公式,簡單描述 roundup_pow_of_two 算法就是向上取最接近的最大 2 的指數次冪,注意此處 backlog 已經是 min(backlog, somaxconn)
半連接隊列大小 = roundup_pow_of_two(max(8, min(backlog, tcp_max_syn_backlog))+1)
代碼里 max_qlen_log 在一個 for 循環里計算,比如算出的半連接隊列大小 nr_table_entries = 16 = 2^4 , 那么 max_qlen_log = 4 , 該值在判斷半連接隊列是否溢出時會用到 。
舉個例子 , 如果 listen backlog = 10、somaxconn = 128、tcp_max_syn_backlog = 128,那么半連接隊列大小 = 16 , 全連接隊列大小 = 10 。
所以要知道 , 在做連接隊列大小調優的時候,一定要綜合上述三個參數,只修改某一個起不到想要的效果 。
連接隊列大小查看全連接隊列大小
可以通過 linux 提供的 ss 命令來查看全連接隊列的大小
Tomcat 調優之從 Linux 內核源碼層面看 Tcp backlog

文章插圖
參數說明,參數很多,其他參數可以自己 help 查看說明
l:表示顯示 listening 狀態的 socket
n:不解析服務名稱
t:只顯示 tcp sockets
這個命令結果怎么解讀呢?
主要看前三個字段,Recv-Q 和 Send-Q 在 State 為 LISTEN 和非 LISTEN 狀態時代表不同的含義 。
State: LISTEN
Recv-Q: 全連接隊列的當前長度,也就是已經完成三次握手等待服務端調用 accept() 方法獲取的連接數量
Send-Q: 全連接隊列的最大長度,也就是我們上述分析的 backlog 和somaxconn 的最小值
State: 非 LISTEN
Recv-Q: 已接受但未被應用進程讀取的字節數
Send-Q: 已發送但未收到確認的字節數
以上區別從如下內核代碼也可以看出,ss 命令就是從 tcp_diag 模塊獲取的數據
Tomcat 調優之從 Linux 內核源碼層面看 Tcp backlog

文章插圖
半連接隊列大小
半連接隊列沒有像 ss 這種命令直接查看,但服務端處于 SYN_RECV 狀態的連接都在半連接隊列里,所以可以通過如下命令間接統計

推薦閱讀