说过了服务器启动,最后来看一下请求处理过程, 服务器启动好后,处于待命状态,请求来了,请求处理过程由分两个建阶段:
-
请求连接建立过程 ( 以 NIO 为例 )
前面有提到,从线程池中固定分配了一个线程专门用于等待新连接,就是上图的监听线程,没有请求来时,该线程是阻塞在 accept () 方法上的,当新连接来建立连接时, accept 方法分配了一个 socket ,并将其设置为 nonblocking, 最后要做的就是将该 socket 丢给某个 Acceptor 线程 ( 基本上机会均等 ) 处理,然后立马返回继续处于接受状态,可以这个线程的工作是相当的简单的,效率那也是相当的高。
Acceptor 线程有很多个 ( 全部来自于线程池,并且固定分配出来,基于 jetty.xml 配置中的 Acceptors 配置数量 ) ,每个线程都维护了一个 SelectSet, 每个 SelectSet 又对应了一个 Selector, 这些线程会检测当前是否有任务来,例如检测 changes 队列中是否有任务,有并且是新连接,那么就迅速建立一个 endpoint 点负责管理这个 socket ,并注册 read 事件,后续该 selector 就会负责该连接的 read 事件监听。
对于连接很多的情况,这里分很多个 Selector 来分别监听,提高了效率。
-
请求数据处理过程 ( 以 NIO 为例 )
当数据发送过来时, Selector 检测到 read 事件,会立马调用 endpoint 的 schedule() 方法,该方法目的就是从线程池分配一个 worker 线程专门来处理这个 read 事件,而自己却立马返回继续监听,可见,这里也是一个高效的处理方式。
业务线程分配成功后,负责请求的读取以及解析,如果请求是完整的,那么就开始调用 Server 的 handle 方法 (server 本身就是一个 handler) ,开始 handler 的处理,最后调用到 SerlvetHandler ,最终交给 Servlet 、 Filter ,开始了我们的自己应用。
后记
1、 Jetty 的模块化做得非常好,可以随时替换其中的绝大部分关键部件,也可以拆掉,例如不需要处理 Session ,可以简单配置一下即可搞定,不需要处理 Servlet, 可以不用配置 ServletHandler.
2、 jetty 采用非阻塞 IO 时,我们可以看到从头到尾的几次线程池分配情况, 第一次 分配一个固定线程监听新连接, 第二次 分配 N 个固定线程监听 read 事件(这里的 N 个线程在 7.3 版本中配置文件中配置 acceptors 数量即可,也就是说会从线程池固定分配 N 个线程出来), 第三次 分配线程就是 read 事件到来之后,立即分配一个业务线程 ( 这个是临时的,用了要回收 ) 处理数据直到我们应用返回结果。 最后有一个地方 上面都没有说到,那就是超时等原因要关闭连接时,是分配了临时线程来处理这些事情
3、 模块化、切分 task
4、 小,真的很小
延伸阅读
1、 Jetty continuations
2、 Tomcat comet