Tomcat架构解析之3 Connector NIO

简介: 上文简单记录了默认的Connector的内部构造及消息流,同时此Connector也是基于BIO的实现。除BIO,也可以通过配置快速部署NIO的connector。

上文简单记录了默认的Connector的内部构造及消息流,同时此Connector也是基于BIO的实现。
除BIO,也可以通过配置快速部署NIO的connector。在server.xml中如下配置;

img_2f5d7a57fd2a97401a9f327c1e14bc63.png

整个Tomcat是一个比较完善的框架体系,各组件间都是基于接口实现,方便扩展
像这里的 org.apache.coyote.http11.Http11NioProtocol和BIO的 org.apache.coyote.http11.Http11Protocol都是统一的实现 org.apache.coyote.ProtocolHandler接口
img_e6f589074daef711e9b77c14a5163e7a.png
ProtocolHandler的实现类

从整体结构上来说,NIO还是与BIO的实现保持大体一致
img_635710f3f40e8dcc949d9f5a05f9b3e7.jpe
NIO connector的内部结构

还是可以看见 Connector中三大件

  • Http11NioProtocol
  • Mapper
  • CoyoteAdapter

基本功能与BIO的类似
重点看看Http11NioProtocol.

img_e7e10667751686dda7667d058d19cd7f.png

img_8eade6a3adef40b2fc97e71e51dd5c58.png

和JIoEndpoint一样, NioEndpointHttp11NioProtocol中负责接收处理 socket的主要模块
img_499e83b135500f8bf4cd39a8b0c9b366.jpe
NioEndpoint的主要流程

AcceptorWorker分别是以线程池形式存在
Poller是一个单线程
注意,与BIO的实现一样,默认状态下,在server.xml中

  • 没有配置<Executor>,则以Worker线程池运行
  • 配置了<Executor>,则以基于juc 系列的ThreadPoolExecutor线程池运行。

Acceptor

  • 接收socket线程,这里虽然是基于NIO的connector,但是在接收socket方面还是传统的serverSocket.accept()方式,获得SocketChannel对象
  • 然后封装在一个tomcat的实现类org.apache.tomcat.util.net.NioChannel对象中
  • 然后将NioChannel对象封装在一个PollerEvent对象中,并将PollerEvent对象压入events queue里。这里是个典型的生产者-消费者模式,Acceptor与Poller线程之间通过queue通信,Acceptor是events queue的生产者,Poller是events queue的消费者。

Poller

Poller线程中维护了一个Selector对象,NIO就是基于Selector来完成逻辑的
Connector中并不止一个Selector,在Socket的读写数据时,为了控制timeout也有一个Selector,在后面的BlockSelector中介绍。可以先把Poller线程中维护的这个Selector标为主Selector
Poller是NIO实现的主要线程。首先作为events queue的消费者,从queue中取出PollerEvent对象,然后将此对象中的channel以OP_READ事件注册到主Selector中,然后主Selector执行select操作,遍历出可以读数据的socket,并从Worker线程池中拿到可用的Worker线程,然后将socket传递给Worker。整个过程是典型的NIO实现。

Worker

Worker线程拿到Poller传过来的socket后,将socket封装在SocketProcessor对象中。然后从Http11ConnectionHandler中取出Http11NioProcessor对象,从Http11NioProcessor中调用CoyoteAdapter的逻辑,跟BIO实现一样。在Worker线程中,会完成从socket中读取http request,解析成HttpServletRequest对象,分派到相应的servlet并完成逻辑,然后将response通过socket发回client。在从socket中读数据和往socket中写数据的过程,并没有像典型的非阻塞的NIO的那样,注册OP_READ或OP_WRITE事件到主Selector,而是直接通过socket完成读写,这时是阻塞完成的,但是在timeout控制上,使用了NIO的Selector机制,但是这个Selector并不是Poller线程维护的主Selector,而是BlockPoller线程中维护的Selector,称之为辅Selector。

NioSelectorPool

NioEndpoint对象中维护了一个NioSelecPool对象,这个NioSelectorPool中又维护了一个BlockPoller线程,这个线程就是基于辅Selector进行NIO的逻辑。以执行servlet后,得到response,往socket中写数据为例,最终写的过程调用NioBlockingSelector的write方法。

public int write(ByteBuffer buf, NioChannel socket, long writeTimeout,MutableInteger lastWrite) throws IOException {  
        SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());  
        if ( key == null ) throw new IOException("Key no longer registered");  
        KeyAttachment att = (KeyAttachment) key.attachment();  
        int written = 0;  
        boolean timedout = false;  
        int keycount = 1; //assume we can write  
        long time = System.currentTimeMillis(); //start the timeout timer  
        try {  
            while ( (!timedout) && buf.hasRemaining()) {  
                if (keycount > 0) { //only write if we were registered for a write  
                    //直接往socket中写数据  
                    int cnt = socket.write(buf); //write the data  
                    lastWrite.set(cnt);  
                    if (cnt == -1)  
                        throw new EOFException();  
                    written += cnt;  
                    //写数据成功,直接进入下一次循环,继续写  
                    if (cnt > 0) {  
                        time = System.currentTimeMillis(); //reset our timeout timer  
                        continue; //we successfully wrote, try again without a selector  
                    }  
                }  
                //如果写数据返回值cnt等于0,通常是网络不稳定造成的写数据失败  
                try {  
                    //开始一个倒数计数器   
                    if ( att.getWriteLatch()==null || att.getWriteLatch().getCount()==0) att.startWriteLatch(1);  
                    //将socket注册到辅Selector,这里poller就是BlockSelector线程  
                    poller.add(att,SelectionKey.OP_WRITE);  
                    //阻塞,直至超时时间唤醒,或者在还没有达到超时时间,在BlockSelector中唤醒  
                    att.awaitWriteLatch(writeTimeout,TimeUnit.MILLISECONDS);  
                }catch (InterruptedException ignore) {  
                    Thread.interrupted();  
                }  
                if ( att.getWriteLatch()!=null && att.getWriteLatch().getCount()> 0) {  
                    keycount = 0;  
                }else {  
                    //还没超时就唤醒,说明网络状态恢复,继续下一次循环,完成写socket  
                    keycount = 1;  
                    att.resetWriteLatch();  
                }  
  
                if (writeTimeout > 0 && (keycount == 0))  
                    timedout = (System.currentTimeMillis() - time) >= writeTimeout;  
            } //while  
            if (timedout)   
                throw new SocketTimeoutException();  
        } finally {  
            poller.remove(att,SelectionKey.OP_WRITE);  
            if (timedout && key != null) {  
                poller.cancelKey(socket, key);  
            }  
        }  
        return written;  
    }  

也就是说当socket.write()返回0时,说明网络状态不稳定,这时将socket注册OP_WRITE事件到辅Selector,由BlockPoller线程不断轮询这个辅Selector,直到发现这个socket的写状态恢复了,通过那个倒数计数器,通知Worker线程继续写socket动作。

看一下BlockSelector线程的逻辑;

public void run() {  
            while (run) {  
                try {  
                    ......  
  
                    Iterator iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;  
                    while (run && iterator != null && iterator.hasNext()) {  
                        SelectionKey sk = (SelectionKey) iterator.next();  
                        KeyAttachment attachment = (KeyAttachment)sk.attachment();  
                        try {  
                            attachment.access();  
                            iterator.remove(); ;  
                            sk.interestOps(sk.interestOps() & (~sk.readyOps()));  
                            if ( sk.isReadable() ) {  
                                countDown(attachment.getReadLatch());  
                            }  
                            //发现socket可写状态恢复,将倒数计数器置位,通知Worker线程继续  
                            if (sk.isWritable()) {  
                                countDown(attachment.getWriteLatch());  
                            }  
                        }catch (CancelledKeyException ckx) {  
                            if (sk!=null) sk.cancel();  
                            countDown(attachment.getReadLatch());  
                            countDown(attachment.getWriteLatch());  
                        }  
                    }//while  
                }catch ( Throwable t ) {  
                    log.error("",t);  
                }  
            }  
            events.clear();  
            try {  
                selector.selectNow();//cancel all remaining keys  
            }catch( Exception ignore ) {  
                if (log.isDebugEnabled())log.debug("",ignore);  
            }  
        }  

使用这个辅Selector主要是减少线程间的切换,同时还可减轻主Selector的负担。以上描述了NIO connector工作的主要逻辑,可以看到在设计上还是比较精巧的。NIO connector还有一块就是Comet,有时间再说吧。需要注意的是,上面从Acceptor开始,有很多对象的封装,NioChannel及其KeyAttachment,PollerEvent和SocketProcessor对象,这些不是每次都重新生成一个新的,都是NioEndpoint分别维护了它们的对象池;

ConcurrentLinkedQueue<SocketProcessor> processorCache = new ConcurrentLinkedQueue<SocketProcessor>()  
ConcurrentLinkedQueue<KeyAttachment> keyCache = new ConcurrentLinkedQueue<KeyAttachment>()  
ConcurrentLinkedQueue<PollerEvent> eventCache = new ConcurrentLinkedQueue<PollerEvent>()  
ConcurrentLinkedQueue<NioChannel> nioChannels = new ConcurrentLinkedQueue<NioChannel>()  

当需要这些对象时,分别从它们的对象池获取,当用完后返回给相应的对象池,这样可以减少因为创建及GC对象时的性能消耗

目录
相关文章
|
人工智能 监控 Java
从NIO server到epoll源码解析
本文深入探讨了Java NIO与Linux epoll的结合使用,通过代码示例和源码分析,详细解析了NIO服务器的实现原理。首先介绍了NIO server demo,展示了如何利用Selector和Channel完成高并发网络框架的构建。接着通过源码探索,揭示了Selector、ServerSocketChannel等核心组件与epoll_create、epoll_ctl、epoll_wait三个核心方法的具体交互过程。最后总结了Java NIO的工作原理:通过Selector管理事件监听,将文件描述符注册到epoll_event中,利用epoll机制实现高效的IO多路复用。
243 0
|
机器学习/深度学习 文字识别 监控
安全监控系统:技术架构与应用解析
该系统采用模块化设计,集成了行为识别、视频监控、人脸识别、危险区域检测、异常事件检测、日志追溯及消息推送等功能,并可选配OCR识别模块。基于深度学习与开源技术栈(如TensorFlow、OpenCV),系统具备高精度、低延迟特点,支持实时分析儿童行为、监测危险区域、识别异常事件,并将结果推送给教师或家长。同时兼容主流硬件,支持本地化推理与分布式处理,确保可靠性与扩展性,为幼儿园安全管理提供全面解决方案。
667 3
|
网络协议 Java 应用服务中间件
框架源码私享笔记(01)Tomcat核心架构功能 | 配置详解
本文首先分享了《活出意义来》一书序言中的感悟,强调成功如同幸福,不是刻意追求就能得到,而是全心投入时的副产品。接着探讨了Tomcat的核心功能与架构解析,包括网络连接器(Connector)和Servlet容器(Container),并介绍了其处理HTTP请求的工作流程。文章还详细解释了Tomcat的server.xml配置文件,涵盖了从顶级容器Server到子组件Connector、Engine、Host、Context等的配置参数及作用,帮助读者理解Tomcat的内部机制和配置方法。
|
机器学习/深度学习 缓存 自然语言处理
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
Tiktokenizer 是一款现代分词工具,旨在高效、智能地将文本转换为机器可处理的离散单元(token)。它不仅超越了传统的空格分割和正则表达式匹配方法,还结合了上下文感知能力,适应复杂语言结构。Tiktokenizer 的核心特性包括自适应 token 分割、高效编码能力和出色的可扩展性,使其适用于从聊天机器人到大规模文本分析等多种应用场景。通过模块化设计,Tiktokenizer 确保了代码的可重用性和维护性,并在分词精度、处理效率和灵活性方面表现出色。此外,它支持多语言处理、表情符号识别和领域特定文本处理,能够应对各种复杂的文本输入需求。
1658 6
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
|
运维 监控 持续交付
微服务架构解析:跨越传统架构的技术革命
微服务架构(Microservices Architecture)是一种软件架构风格,它将一个大型的单体应用拆分为多个小而独立的服务,每个服务都可以独立开发、部署和扩展。
3868 37
微服务架构解析:跨越传统架构的技术革命
|
存储 机器学习/深度学习 应用服务中间件
阿里云服务器架构解析:从X86到高性能计算、异构计算等不同架构性能、适用场景及选择参考
当我们准备选购阿里云服务器时,阿里云提供了X86计算、ARM计算、GPU/FPGA/ASIC、弹性裸金属服务器以及高性能计算等多种架构,每种架构都有其独特的特点和适用场景。本文将详细解析这些架构的区别,探讨它们的主要特点和适用场景,并为用户提供选择云服务器架构的全面指南。
1348 18
|
监控 Java 应用服务中间件
Tomcat log日志解析
理解和解析Tomcat日志文件对于诊断和解决Web应用中的问题至关重要。通过分析 `catalina.out`、`localhost.log`、`localhost_access_log.*.txt`、`manager.log`和 `host-manager.log`等日志文件,可以快速定位和解决问题,确保Tomcat服务器的稳定运行。掌握这些日志解析技巧,可以显著提高运维和开发效率。
1775 13
|
算法 前端开发 定位技术
地铁站内导航系统解决方案:技术架构与核心功能设计解析
本文旨在分享一套地铁站内导航系统技术方案,通过蓝牙Beacon技术与AI算法的结合,解决传统导航定位不准确、路径规划不合理等问题,提升乘客出行体验,同时为地铁运营商提供数据支持与增值服务。 如需获取校地铁站内智能导航系统方案文档可前往文章最下方获取,如有项目合作及技术交流欢迎私信我们哦~
1321 1
|
存储 人工智能 并行计算
2025年阿里云弹性裸金属服务器架构解析与资源配置方案
🚀 核心特性与技术创新:提供100%物理机性能输出,支持NVIDIA A100/V100 GPU直通,无虚拟化层损耗。网络与存储优化,400万PPS吞吐量,ESSD云盘IOPS达100万,RDMA延迟<5μs。全球部署覆盖华北、华东、华南及海外节点,支持跨地域负载均衡。典型应用场景包括AI训练、科学计算等,支持分布式训练和并行计算框架。弹性裸金属服务器+OSS存储+高速网络综合部署,满足高性能计算需求。
|
XML Java 开发者
Spring底层架构核心概念解析
理解 Spring 框架的核心概念对于开发和维护 Spring 应用程序至关重要。IOC 和 AOP 是其两个关键特性,通过依赖注入和面向切面编程实现了高效的模块化和松耦合设计。Spring 容器管理着 Beans 的生命周期和配置,而核心模块为各种应用场景提供了丰富的功能支持。通过全面掌握这些核心概念,开发者可以更加高效地利用 Spring 框架开发企业级应用。
502 18

推荐镜像

更多
  • DNS