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 状态的连接都在半连接队列里,所以可以通过如下命令间接统计

推荐阅读