(三) Tomcat 源码系列之 Tomcat 线程模型

Tomcat 支持的应用层协议 : HTTP/1.1, HTTPS, AJP, 共有三种连接器模式 : BIO, NIO, APR, 在默认的配置下,使用的是 NIO 模式

对于一个请求, Linux 是这样处理的 :

(三) Tomcat 源码系列之 Tomcat 线程模型_第1张图片

  • 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 是这样处理的 :

(三) Tomcat 源码系列之 Tomcat 线程模型_第2张图片

  • 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 是事件队列的消费者
  • SocketProcessor 线程 : 它是线程池的的工作线程,用于处理 Socket 的读写事件

    • 监听到事件发生, 判断 SelectionKey 是哪种事件, 然后将 Selector 监听到的 IO 读写事件封装成 SocketProcessor,交给线程池工作线程处理
    • Tomcat 的线程池区别于 JDK 的线程池, Tomcat 实现类自定义的阻塞队列 TaskQueue , 重写了 offer 方法。当线程数小于最大线程数的时候就直接返回 false (即入队列失败),则迫使线程池建出新的非核心线程。

启动 Tomcat, 利用 IDEA 看到所有的线程状态 :

(三) Tomcat 源码系列之 Tomcat 线程模型_第3张图片

可以发现, Tomcat 的线程模型类似于 主从 Reactor 多线程 线程模型

Connector 结构

(三) Tomcat 源码系列之 Tomcat 线程模型_第4张图片

先由 SocketProcessor 创建 Http11Processor 对象,通过调用 Http11Processor#service 方法对 HTTP 请求进行处理,主要包括创建 org.apache.coyote.Requestorg.apache.coyote.Response 两个对象,并对 HTTP 的请求报文进行解析后保存在 Request 对象中

最后,通过 CoyoteAdapter 的 service 方法,完成 org.apache.coyote.Requestorg.apache.catalina.connector.Requestorg.apache.catalina.connector.Responseorg.apache.catalina.connector.Response 的转换,并将这两个对象交接给 Service 进行处理

你可能感兴趣的:(tomcat)