深入拆解TomcatJetty——Tomcat如何实现IO多路复用

news2024/12/28 21:15:53

深入拆解Tomcat&Jetty

专栏地址: 极客时间-深入拆解Tomcat & Jetty

IO 多路复用

当用户线程发起 I/O 操作后,网络数据读取操作会经历两个步骤:

  • 用户线程等待内核将数据从网卡拷贝到内核空间。
  • 内核将数据从内核空间拷贝到用户空间。

IO 多路复用 是 Linux 五种 IO 模型中的一种,逻辑如下图:

image-20241025140303833

这时用户线程读取数据分为了两步:

  • 间断的发起 select 调用询问内核数据是否已经准备好
  • 在数据就绪后发起 read 系统调用,注意在数据从内核空间拷贝到用户空间时,线程依然是阻塞的

多路复用体现在一次 select 调用可以查询多个 数据通道(channel) 上的数据是否已经准备好

Tomcat 如何实现多路复用模型

对于 Java 的多路复用器的使用,无非是两步:

  • 创建一个 Selector,在它身上注册各种感兴趣的事件,然后调用 select 方法,等待感兴趣的事情发生。
  • 感兴趣的事情发生了,比如可以读了,这时便创建一个新的线程从 Channel 中读数据。

Tomcat 的 NioEndpoint 组件虽然实现比较复杂,但基本原理就是上面两步。它一共包含 LimitLatch、Acceptor、Poller、SocketProcessor 和 Executor 共 5 个组件,它们的工作过程如下图所示:

img

  • LimitLatch:连接控制器,它负责控制最大连接数,NIO 模式下默认是 10000,达到这个阈值后,连接请求被拒绝(这里的拒绝指的是应用层意义上的拒绝,操作系统依然会接收 socket 连接,直到等待队列满)。
  • Acceptor 跑在一个单独的线程里,它在一个死循环里调用 accept 方法来接收新连接,一旦有新的连接请求到来,accept 方法返回一个 Channel 对象,接着把 Channel 对象交给 Poller 去处理。
  • Poller 的本质是一个 Selector,也跑在单独线程里。Poller 在内部维护一个 Channel 数组,它在一个死循环里不断检测 Channel 的数据就绪状态,一旦有 Channel 可读,就生成一个 SocketProcessor 任务对象扔给 Executor 去处理。
  • Executor 就是线程池,负责运行 SocketProcessor 任务类,SocketProcessor 的 run 方法会调用 Http11Processor 来读取和解析请求数据。Http11Processor 是应用层协议的封装,它会调用容器获得响应,再把响应通过 Channel 写出。

LimitLatch(org.apache.tomcat.util.threads.LimitLatch)

部分核心代码如下:

public class LimitLatch {
    private class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected int tryAcquireShared() {
            long newCount = count.incrementAndGet();
            if (newCount > limit) {
                count.decrementAndGet();
                return -1;
            } else {
                return 1;
            }
        }

        @Override
        protected boolean tryReleaseShared(int arg) {
            count.decrementAndGet();
            return true;
        }
    }

    private final Sync sync;
    private final AtomicLong count;
    private volatile long limit;
    
    //线程调用这个方法来获得接收新连接的许可,线程可能被阻塞
    public void countUpOrAwait() throws InterruptedException {
      sync.acquireSharedInterruptibly(1);
    }

    //调用这个方法来释放一个连接许可,那么前面阻塞的线程可能被唤醒
    public long countDown() {
      sync.releaseShared(0);
      long result = getCount();
      return result;
   }
    
}
  • 内部类 Sync 继承了 AQS,AQS 是 Java 并发包中的一个核心类,它在内部维护一个状态和一个线程队列,可以用来控制线程什么时候挂起,什么时候唤醒。
  • 用户线程通过调用 LimitLatch 的 countUpOrAwait 方法来拿到锁,如果暂时无法获取,这个线程会被阻塞到 AQS 的队列中。而这个方法实际会调用拓展类所重写的 tryAcquireShared 方法,它的实现逻辑是如果当前连接数 count 小于 limit,线程能获取锁,返回 1,否则返回 -1。
  • 如果用户线程被阻塞到了 AQS 的队列,同样是由 Sync 内部类决定唤醒,Sync 重写了 AQS 的 tryReleaseShared() 方法,其实就是当一个连接请求处理完了,这时又可以接收一个新连接了,这样前面阻塞的线程将会被唤醒。

Acceptor(org.apache.tomcat.util.net.Acceptor)

Acceptor 实现了 Runnable 接口,因此可以跑在单独线程里。

一个端口号只能对应一个 ServerSocketChannel,因此这个 ServerSocketChannel 是在多个 Acceptor 线程之间共享的,它是 Endpoint 的属性,由 Endpoint 完成初始化和端口绑定。初始化过程如下:

// org.apache.tomcat.util.net.NioEndpoint.initServerSocket()
serverSock = ServerSocketChannel.open();
// getAcceptCount() Acceptor负责从ACCEPT队列中取出连接,当Acceptor处理不过来时,连接就堆积在ACCEPT队列中,默认100
serverSock.socket().bind(addr, getAcceptCount());
serverSock.configureBlocking(true);

ServerSocketChannel 通过 accept() 接受新的连接,accept() 方法返回获得 SocketChannel 对象,然后将 SocketChannel 对象封装在一个 PollerEvent 对象中,并将 PollerEvent 对象压入 Poller 的 Queue 里,这是个典型的“生产者 - 消费者”模式,Acceptor 与 Poller 线程之间通过 Queue 通信。

NioEndpoint.start->
    startInternal()->
    startAcceptorThread() {new Thread(acceptor, threadName).start()} -> 
    Acceptor.run(){ socket = endpoint.serverSocketAccept(); endpoint.setSocketOptions(socket); } ->
    NioEndpoint.setSocketOptions(socke){ NioSocketWrapper socketWrapper = new Nio2SocketWrapper(channel, this); poller.register(); } ->
    NioEndpoint.register(socketWrapper){ addEvent(new PollerEvent(socketWrapper, OP_REGISTER)); } ->
    NioEndpoint.events.offer(event){ Poller.events.offer(event); }

Poller(org.apache.tomcat.util.net.NioEndpoint.Poller)

Poller 本质是一个 Selector,它内部维护一个 Queue,这个 Queue 定义如下:

private final SynchronizedQueue<PollerEvent> events = new SynchronizedQueue<>();

SynchronizedQueue 的方法比如 offer、poll、size 和 clear 方法,都使用了 synchronized 关键字进行修饰,用来保证同一时刻只有一个 Acceptor 线程对 Queue 进行读写。同时有多个 Poller 线程在运行(Tomcat9只有一个线程在运行,NioEndpoint#startInternal()),每个 Poller 线程都有自己的 Queue。每个 Poller 线程可能同时被多个 Acceptor 线程调用来注册 PollerEvent。同样 Poller 的个数可以通过 pollers 参数配置。

  • Poller 不断的通过内部的 Selector 对象向内核查询 Channel 的状态,一旦可读就生成任务类 SocketProcessor 交给 Executor 去处理。
  • Poller 的另一个重要任务是循环遍历检查自己所管理的 SocketChannel 是否已经超时,如果有超时就关闭这个 SocketChannel。

SocketProcessor(org.apache.tomcat.util.net.NioEndpoint.SocketProcessor)

Poller 会创建 SocketProcessor 任务类交给线程池处理,而 SocketProcessor 实现了 Runnable 接口,(这里是 SocketProcessorBase 实现了 Runnable 接口,在 run 方法里调用了抽象方法 doRun,SocketProcessor 继承了它并重写了 doRun 方法),用来定义 Executor 中线程所执行的任务,主要就是调用 Http11Processor 组件来处理请求。Http11Processor 读取 Channel 的数据来生成 ServletRequest 对象(Http11Processor#service())。

这里请注意:Http11Processor 并不是直接读取 Channel 的。这是因为 Tomcat 支持同步非阻塞 I/O 模型和异步 I/O 模型,在 Java API 中,相应的 Channel 类也是不一样的,比如有 AsynchronousSocketChannel 和 SocketChannel,为了对 Http11Processor 屏蔽这些差异,Tomcat 设计了一个包装类叫作 SocketWrapper,Http11Processor 只调用 SocketWrapper 的方法去读写数据。

Executor

Executor 是 Tomcat 定制版的线程池,它负责创建真正干活的工作线程,干什么活呢?就是执行 SocketProcessor 的 run 方法,也就是解析请求并通过容器来处理请求,最终会调用到 Servlet。

如何实现高并发

高并发就是能快速地处理大量的请求,需要合理设计线程模型让 CPU 忙起来,尽量不要让线程阻塞,因为一阻塞,CPU 就闲下来了。另外就是有多少任务,就用相应规模的线程数去处理。我们注意到 NioEndpoint 要完成三件事情:接收连接、检测 I/O 事件以及处理请求,那么最核心的就是把这三件事情分开,用不同规模的线程数去处理,比如用专门的线程组去跑 Acceptor,并且 Acceptor 的个数可以配置;用专门的线程组去跑 Poller,Poller 的个数也可以配置;最后具体任务的执行也由专门的线程池来处理,也可以配置线程池的大小。

这其中比较核心的就是把检测IO事件这一操作由少量selector集中处理,避免大量线程占用cpu时间在轮询IO事件上

Java中自身的NIO到底是同步非阻塞,还是IO多路复用

NIO API 可以不用 Selector,就是同步非阻塞。使用了 Selector 就是 IO 多路复用

Tomcat 该组件虽然是叫 NioEndpoint,但使用了 Selector,所以其实是 IO 多路复用

如何理解 IO 操作模型中的同步异步,阻塞非阻塞

同步异步:

  • 同步可以理解为线程在请求 IO 数据后是直接返回,还是阻塞等待数据从网卡(或者其他地方)拷贝到内存空间再到用户空间
  • 异步可以理解为线程在请求 IO 数据后直接返回,但是在请求时注册了一个回调函数,内核将数据准备好后通过回调函数通知

阻塞和非阻塞主要是看发起I/O操作时,内核空间没有数据可读时,线程是否会阻塞等待,直到有数据到来

  • 阻塞:调用 read() 时,如果内核空间中没有数据可读,线程就让出 cpu 阻塞等待,直到内核把数据拷贝到用户空间,唤醒线程,read()调用返回
  • 非阻塞:调用 read() 时,如果内核空间没有可读数据,线程立刻返回,直到再次调用read(),内核空间有数据可读时,阻塞等待内核把数据拷贝到 read() 函数指定的buff中,唤醒线程,read()调用返回

关于 IO 的几篇文章推荐:

【1】https://time.geekbang.org/column/article/100307 Tomcat如何实现IO多路复用

【2】https://time.geekbang.org/column/article/103959 内核如何阻塞与唤醒进程

【3】https://mp.weixin.qq.com/s/LYbJxorhsyoWWtP6OR6-eQ 一顿饭的事儿,搞懂Linux5种IO模型

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2223837.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker搭建DVWA靶场以及刷题记录

Docker搭建DVWA靶场以及刷题记录 Docker搭建DVWA靶场以及刷题记录靶场搭建 刷题记录SQL InjectionLowMediumHighImpossible SQL Injection(Blind)LowMediumHigh XSS (Reflected)LowMediumHigh XSS (Stored)LowMediumHigh File UploadLowMediumHigh Docker搭建DVWA靶场以及刷题记…

zabbix 6.0 监控clickhouse(单机)

zabbix 6.0 LTS已经包含了clickhouse的监控模板&#xff0c;所以我们可以直接使用自带的模板来监控clickhouse了。 0.前置条件 clickhouse 已经安装&#xff0c;我安装的是24.3.5.47zabbix-agent 已经安装并配置。系统是ubuntu 2204 server 1. 新建监控用户 使用xml的方式为…

适用于 Windows 11/10 电脑 的 13 个最佳文件恢复软件

如果您由于系统故障、硬件损坏、人为错误或病毒攻击而丢失了重要文件或文件夹。不用担心&#xff0c;因为我们随时为您提供帮助&#xff01;借助正确的文件恢复工具&#xff0c;您可以立即检索计算机上不同类型的文件。如果你有为您的文件创建备份&#xff0c;你不用担心&#…

juzigei/基于 ThinkPHP+Mysql灵活用工_灵活用工系统_灵活用工平台

基于 ThinkPHPMysql 灵活用工灵活用工平台灵活用工系统灵活用工小程序灵活用工源码灵活用工系统源码 开发语言 ThinkPHPMysql 源码合作 提供完整源代码 软件界面展示 一、企业管理后台 二、运用管理平台 三、手机端

ECharts饼图-饼图自定义样式,附视频讲解与代码下载

引言&#xff1a; 在数据可视化的世界里&#xff0c;ECharts凭借其丰富的图表类型和强大的配置能力&#xff0c;成为了众多开发者的首选。今天&#xff0c;我将带大家一起实现一个饼图图表&#xff0c;通过该图表我们可以直观地展示和分析数据。此外&#xff0c;我还将提供详…

视频播放速度过快怎么办?视频速度调慢的简单方法

视频播放速度过快怎么办&#xff1f;在这个快节奏的时代&#xff0c;我们似乎习惯了迅速浏览和消化信息&#xff0c;视频内容也不例外。然而&#xff0c;有时我们会遇到视频速度过快的问题&#xff0c;这不仅让我们难以捕捉到每一个细节&#xff0c;更可能让我们错失视频所要传…

【ChatGPT插件漏洞三连发之二】零点击Github仓库接管

在我们深入研究细节之前&#xff0c;我们想先解释一下插件上的帐户接管意味着什么。 举个例子&#xff0c;当您安装与您的 GitHub 交互的插件时&#xff0c;此插件会在插件网站上为您创建一个额外的帐户&#xff0c;用于存储您的 GitHub 凭据。使用这些凭据&#xff0c;插件可…

内置数据类型、变量名、字符串、数字及其运算、数字的处理、类型转换

内置数据类型 python中的内置数据类型包括&#xff1a;整数、浮点数、布尔类型&#xff08;以大写字母开头&#xff09;、字符串 变量名 命名变量要见名知意&#xff0c;确保变量名称具有描述性和意义&#xff0c;这样可以使得代码更容易维护&#xff0c;使用_可以使得变量名…

Linux中级(DNS域名解析服务器)

一。产生原因1.IP地址&#xff1a;是互联网上计算机唯一的逻辑地址&#xff0c;通过IP地址实现不同计算机之间的相互通信&#xff0c;每台联网计算机都需要通过IP地址来互相联系和分别&#xff0c;但由于IP地址是由一串容易混淆的数字串构成&#xff0c;人们很难记忆所有计算机…

计算机毕业设计Python+大模型租房推荐系统 租房大屏可视化 租房爬虫 hadoop spark 58同城租房爬虫 房源推荐系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 用到的技术: 1. python…

【独家:AI编程助手Cursor如何revolutionize Java设计模式学习】

【独家:AI编程助手Cursor如何revolutionize Java设计模式学习】 导语 在Java高级编程的世界里,设计模式是每个开发者必须掌握的利器。但是,如何快速理解并灵活运用这些模式呢?让我们一起探索如何借助AI编程助手Cursor,轻松掌握设计模式,提升Java编程技能! 正文 设计模式:J…

opencv - py_ml - py_kmeans

文章目录 1.理解 K-Means 聚类目标理论T 恤尺码问题它是如何工作的&#xff1f; 其他资源 2.OpenCV 中的 K-Means 聚类目标理解参数输入参数输出参数 1.理解 K-Means 聚类 目标 在本章中&#xff0c;我们将理解 K-Means 聚类的概念、其工作原理等。 理论 我们将通过一个常用…

4.2-6 使用Hadoop WebUI

文章目录 1. 查看HDFS集群状态1.1 端口号说明1.2 用主机名访问1.3 主节点状态1.4 用IP地址访问1.5 查看数据节点 2. 操作HDFS文件系统2.1 查看HDFS文件系统2.2 在HDFS上创建目录2.3 上传文件到HDFS2.4 删除HDFS文件和目录 3. 查看YARN集群状态4. 实战总结 1. 查看HDFS集群状态 …

JVM 实战篇(一万字)

此笔记来至于 黑马程序员 内存调优 内存溢出和内存泄漏 内存泄漏&#xff08;memory leak&#xff09;&#xff1a;在Java中如果不再使用一个对象&#xff0c;但是该对象依然在 GC ROOT 的引用链上&#xff0c;这个对象就不会被垃圾回收器回收&#xff0c;这种情况就称之为内…

matlab中,close和close all的区别、clc和clear的区别、

文章目录 1. clear vs. clear all2. clc vs. clear3. hold on vs. hold off4. subplot vs. subplot(221)5. axis tight vs. axis equal6. save vs. saveas总结 在 MATLAB 中&#xff0c;有许多命令和函数在功能上相似&#xff0c;但其用法和效果却有所不同。以下是一些常见的例…

基于SSM+小程序的垃圾分类管理系统(垃圾2)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM小程序的垃圾分类管理系统实现了管理员及用户。 1、管理员功能结构图&#xff0c;管理员功能有个人中心&#xff0c;管理员管理&#xff0c;基础数据管理、论坛管理、垃圾信息管理…

logback 如何将日志输出到文件

如何作 将日志输出到文件需要使用 RollingFileAppender&#xff0c;该 Appender 必须定义 rollingPolicy &#xff0c;另外 rollingPollicy 下必须定义 fileNamePattern 和 encoder <appender name"fileAppender" class"ch.qos.logback.core.rolling.Rollin…

Zabbix企业级分布式监控环境部署

“运筹帷幄之中&#xff0c;决胜千里之外”。在IT运维中&#xff0c;监控占据着重要的地位&#xff0c;按比例来算&#xff0c;说占30%一点也不为过。对IT运维工程师来说&#xff0c;构建一个真正可用的监控告警系统是一项艰巨的任务。在监控系统的开源软件中&#xff0c;可供选…

论文阅读:华为的LiMAC

《LIGHTWEIGHT NEURAL APP CONTROL》 用于app控制的轻量级神经网络 摘要 输入是一个文本目标和一系列过去的移动感知&#xff0c;比如截图和相应的UI树&#xff0c;来生成精确的动作。 针对智能手机固有的计算限制&#xff0c;我们在LiMAC中引入了一个小型Action Transforme…

【安全解决方案】深入解析:如何通过CDN获取用户真实IP地址

一、业务场景 某大型互联网以及电商公司为了防止客户端获取到真实的ip地址&#xff0c;以及达到保护后端业务服务器不被网站攻击&#xff0c;同时又可以让公安要求留存网站日志和排查违法行为&#xff0c;以及打击犯罪的时候&#xff0c;获取不到真实的ip地址&#xff0c;发现…