Tomcat 7 NIO handling request on Linux
围绕这个图, 做几点解说(左边的虚线框表示 Tomcat):
- 从操作系统层面来说, 收到 TCP 的 SYN 之后, 放到 TCP SYN backlog 队列, 然后发送 (syn + ack) 包, 等待对方的 ack 包;
SYN backlog 长度由 /proc/sys/net/ipv4/tcp_max_syn_backlog 设置, 还有文章说最终值有几个因素共同决定, 不过在 Linux Kernel 4.3 之后,这个 syn backlog 不在由 net.ipv4.tcp_max_syn_backlog 决定, 而是由 net.core.somaxconn 决定
eric@host:~$ sysctl net.core.somaxconn net.core.somaxconn = 4096 eric@host:~$ sysctl net.ipv4.tcp_max_syn_backlog net.ipv4.tcp_max_syn_backlog = 4096
如何查看一个监听端口的 SYN backlog 当前的队列长度?
没有直接看当前长度的命令, 不过可以自己手工计算:# 查看连到当前 host 8080 端口上并且处于 sync-recv 状态的连接 eric@host:~$ ss -n state syn-recv sport = :8080
- 如果 SYN backlog 已经满了, 那么操作系统层面会自动丢弃接收到的 syn 包, 那么客户端以为对端没收到, 会继续尝试.
- 当操作系统回复 syn+ack 之后, 对端返回 ack, 完成三次包握手, 这时, 这个连接变成 Established 的状态, 该连接从 syn backlog 进入 listen backlog;
- listen backlog 的长度由 listen 系统调用的参数决定, 在 Java 里由 ServerSocket(int port, int backlog)的构造函数的 backlog 参数决定. java 默认是 50. Tomcat 7 里面由 Connector 参数 acceptCount 决定, 默认是 100;
- 如果这个 listen backlog 满了, OS 收到对端为了完成握手的 ack 时, 选择 ignore 这个 ack, 那么客户端会以为丢包, 会继续尝试发送 ack;
- 如何查看一个监听端口的 listen backlog 队列的当前长度? Linux -> ss -ltn 看 Send-Q 列
- Tomcat 7 NIO 处理这些 socket 连接有 2 类 background 线程, 分别是 Acceptor 线程和 Poller 线程;
- Acceptor 线程 通过 serverSock.accept() 方法接受新连接, 就是 listen backlog 里面已经建立的连接;
- Acceptor 接受之后, 就把这个接受的 socket 送给 Poller 线程去处理;
- Poller 线程把 socket 封装之后放入 TaskQueue;
- Poller 线程还负责 从 socket 的 Channel 搬运数据到对应的 Buffer, 也就是负责 NIO 的 Selector 的处理任务;
- Acceptor 线程和 Poller 线程的数量分别由 Tomcat Connector 的 acceptorThreadCount 和 pollerThreadCount 决定;
- Tomcat 的 TaskQueue 是一个 LinkedBlockingQueue;
- TaskQueue队列里的 Task 由 Executor 里的线程读取并执行;
- TaskQueue队列里的 Task 由 Poller 放入;
- TaskQueue的长度默认是 Int 的最大值, 不过基本不会放这么多, 如果TaskQueue的任务和在处理的超过 maxConnection 的值, Poller 就会决绝新的任务;
参考: https://blog.cloudflare.com/syn-packet-handling-in-the-wild/