Jetty9源码剖析 - Connector组件 - IO模型

转载自ph0ly:http://www.ph0ly.com

一、多路复用IO

在开始正式Jetty IO模型分析前,让我们一起来回顾下IO多路复用模型

IO多路复用模型

IO多路复用也称为事件驱动IO,它通过select(poll、epoll)阻塞调用,当有某个注册的事件发生时,会唤醒用户线程。它利用单个线程就能完成一系列IO操作,相比于传统的阻塞多线程模型来说优点很明显,不需要占用大量线程、更少的CPU占用。不过本质上来说,它仍然是阻塞IO,当事件发生时,用户线程需要执行read、write、accept操作,也就是说该模型是同步非阻塞IO

二、Java NIO

NIO即非阻塞IO,Java底层使用了epoll来实现,即IO多路复用模型

它将操作系统级别的调用抽象为Selector、Channel、Buffer接口。其中Selector负责轮询注册的Channel上面的事件,调用select来阻塞住用户线程,当事件发生时,唤醒用户线程,用户线程可以获取到某些Channel当前事件类型并执行读、写、连接的处理

三、Jetty NIO模型

1. 概述

Jetty作为现代主流Web容器,自然要跟上时代的潮流,它使用JDK提供的NIO来实现自己的通信层,结合自身实现的异步回调机制来模拟异步IO的能力

总体来说Jetty IO和Netty IO模型类似,是一个主从Reactor模型

2. 主从Reactor模型

主从Reactor IO模型

在传统的NIO Reactor模型下,一个线程完成所有事件的处理,包括连接、读、写操作,对于Jetty容器或Netty框架而言,在高并发,大量连接的情况下,仍然能够保证高性能,尤其是连接建立和读写操作,读写操作可能会比较耗时,如果由于读写较慢导致连接处理不过来,那是无法称为高性能的,因此从简单的单线程的Reactor模型演化为多线程模型,即将读写操作独立到一个独立的线程池来完成IO操作,通常这种模型性能基本能保证(虽然存在线程调度的代价),但是部分场景下(例如服务端需要和客户端进行安全认证,认证的过程可能会比较慢),会导致连接的处理线程不够用,因此再将连接处理层调整为一个线程池(或多个实体线程),这样连接的处理是多个线程,同时读写也是多个线程,性能和可靠性都更加强

3. 主从Reactor模型在Jetty IO中的应用

Jetty IO模型

类比于主备Reactor,ServerConnector相当于mainReactor,Acceptor依附于ServerConnector,ManagedSelector相当于subReactor

这个是一个简化版的模型,真实里面还包含很多细节,例如:

1. 连接的接入,正常情况是通过ServerConnector创建的ServerSocketChannel,每个ServerConnector.Acceptor共享这个ServerSocketChannel来阻塞accept得到SocketChannel,这些并没有画出来

2. Acceptor.accept后,首先调用的是ServerConnector的accept方法,做了一些配置才最终到SelectorManager去轮询选ManagedSelector的,这里为了方便大家理解,简化了链路

 

3. ManagedSelector使用了EPC(ExecuteProduceConsume)线程IO执行模型,为了展现一个纯粹的IO模型,这里并没有画出来

从图上我们可以看出:

1. Acceptor作为连接接收器,负责客户端连接接入(Jetty生成Acceptor个数默认由CPU的逻辑核数/8来决定,最大不能超过4个,最少1个),每个Acceptor共享ServerConnector创建的ServerSocketChannel,Acceptor作为Runnable会被扔到qtp线程池执行,也就实现了池化连接处理

2. 连接建立后,会触发ServerConnector的accept,拿到该连接的SocketChannel,这时会将该SocketChannel配置为非阻塞,然后将该SocketChannel轮询选择一个当前容器中ManagedSelector,并调用Selector.register,将这个SocketChannel注册到这个该Selector

3. ManagedSelector是Selector的包装,它具有生命周期(LifeCycle),所以称为Managed(托管的),ManagedSelector通常是逻辑核数/2,最少1个。ManagedSelector.doStart会开一个新的Selector,同时将自己扔到线程池,执行EPC(ExecuteProduceConsume,在同一个线程完成任务生成并执行,同时生产完成后开下一个EPC,执行新的生产消费任务,后续会有专门介绍),EPC会调用ManagedSelector.SelectorProducer.produce方法生产任务,而在ManagedSelector.SelectorProducer.produce,里面主要检测事件(processSelected),Interest集合切换(runActions),等待事件(select),processSelected会检测Channel集合发生的事件,并触发SelectChannelEndPoint.onSelected调用,根据事件类型(通常是读写操作)回调SelectChannelEndPoint.runFillable,该回调最终调到HttpConnection.onFillable,这里会处理各种Http的信息

4. 当应用完成处理后,会触发Response.getWriter()来写入响应数据,最终Jetty会调用到SelectChannelEndPoint.flush操作,该操作会往SocketChannel写入数据(如果没写完,会修改该Channel的SelectionKey为OP_WRITE,下一次读写事件来时,会触发未完成的写,避免CPU 100%)

四、总结

Jetty IO模型也是随着时代的潮流不断演化,从最初的BIO到NIO Reactor,再到如今流行的主备Reactor模型。然而JDK7很早就已经推出了AIO(异步IO),为何Jetty不使用?从Jetty官方文档来看Jetty也尝试过使用AIO,不过最后放弃了,给出的理由是AIO提供的异步语义不够丰富(远程连接关闭事件、异步连接超时等等),无法让Jetty顺滑切换到其上(详见:https://webtide.com/on-jdk-7-asynchronous-io/)。不过相信以后Java官方逐步完善AIO,AIO将会成为Jetty主流的IO实现,毕竟AIO才是真正的异步非阻塞IO

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值