Tomcat 支持的应用层协议 : HTTP/1.1, HTTPS, AJP, 共有三种连接器模式 : BIO, NIO, APR, 在默认的配置下,使用的是 NIO 模式
对于一个请求, Linux 是这样处理的 :
- TCP 的三次握手建立连接,建立连接的过程中,Linux 内核维护了半连接队列 (syn队列) 以及完全连接队列 (accept队列)
syn 队列 : 用来保存处于 SYN_SENT 和 SYN_RECV 状态的请求
accept 队列 : 用来保存处于 ESTABLISHED 状态的请求 - 第一次握手成功后, 会立即将请求封装成 Socket 放入 syn 队列, 并发出 SYN, ACK 报文, 等待 client 的确认
- 在第三次握手之后,server 收到了 client 的确认,则进入 ESTABLISHED 的状态,然后该连接由 syn 队列移动到 accept 队列
- 工作在应用层的 ServerSocketChannel 通过 accept 方法可以取出已经建立连接的的 Socket
- 所以当 ServerSocketChannel 的 accept 方法取出不及时就有可能造成 accept 队列积压,一旦满了连接就被拒绝了
查看 Linux 的内核配置 :
可以清楚的看到, syn 队列的长度默认为 1024, accept 队列的长度默认为 128
Tomcat 的线程模型
前面我们知道, Tomcat 默认采用的连接器模式为 NIO, 所有肯定存在一个线程调用 accept 方法专门用来监听来自 client 的请求
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
根据 serve.xml 配置文件可以得知, 连接器监听的是 8080 端口, 并且监听 HTTP/1.1 协议, 如果是 HTTPS 协议, 连接器会将请求重定向到 8443 端口
所以, 对于一个 HTTP 请求, Tomcat 是这样处理的 :
-
Acceptor 线程 :全局唯一,负责接受请求,并将请求放入 Poller 线程的事件队列
- Accetpr 线程一直循环, 首先判断如果连接数大于阈值 (默认 10000), 就会阻塞该 Acceptor 线程, 拒绝请求.
- 接着调用 accept 方法监听来自 client 的请求 (这是阻塞的), 接收到请求后, 将请求分发给 Poller 线程, 在分发事件的时候,采用的是轮询法
- 即
请求次数 % Poller 线程数
取余, 可以得到一个下标索引 index, pollers[index] 就为处理该请求的 Poller 线程 - 这种方式和
Ribbon
的默认负载均衡方式相同
-
Poller 线程 :默认是两个, 这取决于计算机的核心数, 取这两者之间的最小值
- Poller 线程会一直循环, 首先检查自身的 events 事件队列, 如果队列中存在元素, 则挨个取出队列中的元素, 并就将队列中的 SocketChannel 以
OP_READ
事件注册进自身的 Selector - 在 event 方法之后, 才开始监听事件, 调用 Selector#select() 方法监听, 返回有事件发生的个数, 并设置的超时时间默认是 1 s
- 典型的
生产者-消费者模式
,Acceptor 与 Poller 线程之间通过事件队列通信,Acceptor 是事件队列的生产者, Poller 是事件队列的消费者
- Poller 线程会一直循环, 首先检查自身的 events 事件队列, 如果队列中存在元素, 则挨个取出队列中的元素, 并就将队列中的 SocketChannel 以
-
SocketProcessor 线程 : 它是线程池的的工作线程,用于处理 Socket 的读写事件
- 监听到事件发生, 判断 SelectionKey 是哪种事件, 然后将 Selector 监听到的 IO 读写事件封装成 SocketProcessor,交给线程池工作线程处理
- Tomcat 的线程池区别于 JDK 的线程池, Tomcat 实现类自定义的阻塞队列
TaskQueue
, 重写了 offer 方法。当线程数小于最大线程数的时候就直接返回 false (即入队列失败),则迫使线程池建出新的非核心线程。
启动 Tomcat, 利用 IDEA 看到所有的线程状态 :
可以发现, Tomcat 的线程模型类似于 主从 Reactor 多线程
线程模型
Connector 结构
先由 SocketProcessor 创建 Http11Processor 对象,通过调用 Http11Processor#service 方法对 HTTP 请求进行处理,主要包括创建 org.apache.coyote.Request
与 org.apache.coyote.Response
两个对象,并对 HTTP 的请求报文进行解析后保存在 Request 对象中
最后,通过 CoyoteAdapter 的 service 方法,完成 org.apache.coyote.Request
对 org.apache.catalina.connector.Request
和 org.apache.catalina.connector.Response
对 org.apache.catalina.connector.Response
的转换,并将这两个对象交接给 Service 进行处理