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

邮差的信 提交于 2020-10-03 07:43:38

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" />
1
2
3
根据 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 是事件队列的消费者
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 进行处理
————————————————
版权声明:本文为CSDN博主「GapaU」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Gp_2512212842/java/article/details/107428718

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!