1. 请求处理流程 AprEndPoint

顺着上一篇接着聊,当一个请求发送到 Tomcat 以后,会由连接器 Connector 转送至 AprEndPoint ,在 AprEndPoint 中调用了 startInternal() 方法,这个方法总共做了做了四件事儿:

  • LimitLatch 限制连接次数。
  • 创建了 poller 线程。
  • 创建了 sendfile 线程。
  • 创建了 acceptor 。

其中, pollersendfileacceptor 都是 AprEndPoint 的内部类,因为他们的父类都实现了 Runnable ,所以核心逻辑都在他们自己的 run() 方法中。

其中的涉及到的源代码太多了,我就是懒得往出列了,所以画了下面这个图给各位做个示意。

  • LimitLatch 是连接控制器,它负责控制最大连接数。
  • Acceptor 跑在一个单独的线程中,它在一个死循环里面通过调用 accept() 方法来接收新连接,会返回一个 long 类型的 socket ,然后将这个 socket 封装成 AprSocketWrapper 对象。
  • Poller 本身也跑在一个单独的线程中,它早内部维护了一个 SocketList 对象,这个对象中含有 socket 数组,它在一个死循环里不断检测 socket 的数据就绪状态,一旦有 socket 可读,就生成一个 SocketProcessor 任务对象扔给 Executor 去处理。
  • Executor 就是一个线程池,负责运行 SocketProcessor 任务类, SocketProcessorrun() 方法会调用 Http11Processor 来读取和解析请求数据。

肯能有的朋友看完了,都不知道 AprEndPoint 或者说 Apr 这种连接模式是什么。

稍微做下简介:

APR(Apache Portable Runtime Libraries)是 Apache 可移植运行时库,它是用 C 语言实现的,其目的是向上层应用程序提供一个跨平台的操作系统接口库。Tomcat 可以用它来处理包括文件和网络 I/O,从而提升性能。

在 Tomcat8.5.x 中,默认的 I/O 模式使用的是 NIO ,使用的链接器是 org.apache.coyote.http11.Http11NioProtocol ,当然,由于是默认的,无需显示配置,在 server.xml 中只需要这么写就可以了:

<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />

但是如果要换成 APR ,就需要这么写了:

<Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
maxThreads="150" SSLEnabled="true" >
<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
<SSLHostConfig>
<Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
certificateFile="conf/localhost-rsa-cert.pem"
certificateChainFile="conf/localhost-rsa-chain.pem"
type="RSA" />
</SSLHostConfig>
</Connector>

接下来聊一个拷问灵魂的问题, APR 是如何提升性能的?

NioEndpoint 一样, AprEndpoint 也实现了非阻塞 I/O,它们的区别是:NioEndpoint 通过调用 Java 的 NIO API 来实现非阻塞 I/O,而 AprEndpoint 是通过 JNI 调用 APR 本地库而实现非阻塞 I/O 的。

Tomcat 的 Endpoint 组件在接收网络数据时需要预先分配好一块 Buffer,所谓的 Buffer 就是字节数组 byte[] ,Java 通过 JNI 调用把这块 Buffer 的地址传给 C 代码,C 代码通过操作系统 API 读取 Socket 并把数据填充到这块 Buffer。

Java NIO API 提供了两种 Buffer 来接收数据: HeapByteBuffer 和 DirectByteBuffer 。

HeapByteBuffer 对象本身在 JVM 堆上分配,并且它持有的字节数组 byte[] 也是在 JVM 堆上分配。但是如果用 HeapByteBuffer 来接收网络数据,需要把数据从内核先拷贝到一个临时的本地内存,再从临时本地内存拷贝到 JVM 堆,而不是直接从内核拷贝到 JVM 堆上。

数据从内核拷贝到 JVM 堆的过程中,JVM 可能会发生 GC , GC 过程中对象可能会被移动,也就是说 JVM 堆上的字节数组可能会被移动,这样的话 Buffer 地址就失效了。如果这中间经过本地内存中转,从本地内存到 JVM 堆的拷贝过程中 JVM 可以保证不做 GC。

Tomcat 的 AprEndpoint 通过操作系统层面的 sendfile 特性解决了这个问题,sendfile 系统调用方式非常简洁。

2. 请求处理流程 NioEndPoint

前面介绍了 AprEndpoint 的请求处理流程,我们在顺便看下 Tomcat 默认的 NioEndPoint 处理流程。

实际上这两个处理流程非常的相似,区别基本上是因为非阻塞 I/O 的实现方式。

  • Acceptor 中的 accept() 方法返回一个 Channel 对象,接着把 Channel 对象交给 Poller 去处理。
  • Poller 在内部维护一个 Channel 数组,它在一个死循环里不断检测 Channel 的数据就绪状态,一旦有 Channel 可读,就生成一个 SocketProcessor 任务对象扔给 Executor 去处理。每个 Poller 线程都有自己的 Queue 。每个 Poller 线程可能同时被多个 Acceptor 线程调用来注册 PollerEventPoller 不断的通过内部的 Selector 对象向内核查询 Channel 的状态,一旦可读就生成任务类 SocketProcessor 交给 Executor 去处理。 Poller 的另一个重要任务是循环遍历检查自己所管理的 SocketChannel 是否已经超时,如果有超时就关闭这个 SocketChannel
  • Executor 是线程池,负责运行 SocketProcessor 任务类, SocketProcessorrun() 方法会调用 Http11Processor 来读取和解析请求数据。 ServerSocketChannel 通过 accept() 接受新的连接, accept() 方法返回获得 SocketChannel 对象,然后将 SocketChannel 对象封装在一个 PollerEvent 对象中,并将 PollerEvent 对象压入 PollerQueue 里,这是个典型的生产者 - 消费者模式, AcceptorPoller 线程之间通过 Queue 通信。

参考

https://jonhuster.blog.csdn.net/article/details/93297251

Tomcat 第五篇:请求处理流程(下)的更多相关文章

  1. 学习java随笔第五篇:流程控制

    条件语句 if(表达式){方法体}else if(表达体)else{方法体} 简写形式:if... 一般形式:if...else... 完整形式:if...else if...else 分支语句 sw ...

  2. .Net基础篇_学习笔记_第五天_流程控制while循环

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  3. Webflux请求处理流程

    spring mvc处理流程 在了解SpringMvc的请求流程源码之后,理解WebFlux就容易的多,毕竟WebFlux处理流程是模仿Servlet另起炉灶的. 下面是spring mvc的请求处理 ...

  4. ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程

    从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...

  5. ASP.Net请求处理机制初步探索之旅 - Part 5 ASP.Net MVC请求处理流程

    好听的歌 我一直觉得看一篇文章再听一首好听的歌,真是种享受.于是,我在这里嵌入一首好听的歌,当然你觉得不想听的话可以点击停止,歌曲 from 王菲 <梦中人>: --> 开篇:上一篇 ...

  6. Http 请求处理流程

    引言 我查阅过不少Asp.Net的书籍,发现大多数作者都是站在一个比较高的层次上讲解Asp.Net.他们耐心.细致地告诉你如何一步步拖放控件.设置控件属性.编写CodeBehind代码,以实现某个特定 ...

  7. 第五篇 :微信公众平台开发实战Java版之如何获取公众号的access_token以及缓存access_token

    一.access_token简介 为了使第三方开发者能够为用户提供更多更有价值的个性化服务,微信公众平台 开放了许多接口,包括自定义菜单接口.客服接口.获取用户信息接口.用户分组接口.群发接口等, 开 ...

  8. 第五篇 Getting Started with ORACLE EBS(开始学习ORACLE EBS)

    第一篇介绍了ERP软件是供应链管理软件.告诉你这个软件改善或提升企业管理的切入点和着力点.有了着力点才能给力. 第二篇介绍了什么是咨询以及咨询工作共通的章法,告诉了你咨询的套路是什么,就像练习一套拳, ...

  9. ASP.Net MVC请求处理流程

    ASP.Net MVC请求处理流程 好听的歌 我一直觉得看一篇文章再听一首好听的歌,真是种享受.于是,我在这里嵌入一首好听的歌,当然你觉得不想听的话可以点击停止,歌曲 from 王菲 <梦中人& ...

随机推荐

  1. 5分钟快速学会xpath定位

    今天我们先来和大家说一下appium,首先教大家如何定位xpath,五分钟即可学会:例:现在我想定位下面这个登录按钮: xpath该怎么写呢? 先不管三七二十几,先写//,然后找你要定位元素最近的可以 ...

  2. Python开发的入门教程(六)-函数

    介绍 本文主要介绍Python中函数的基本知识和使用 Python之什么是函数 我们知道圆的面积计算公式为: S = πr² 当我们知道半径r的值时,就可以根据公式计算出面积.假设我们需要计算3个不同 ...

  3. 蒲公英 · JELLY技术周刊 Vol.20: Vue3 极致优化——分析 Vue3 Compiler 告诉你为什么这么快

    蒲公英 · JELLY技术周刊 Vol.20 性能优化是一条无尽的路,我们总是可以找到各种途径去提升体验,不论是响应时间还是按需加载,亦或是根据框架或者组件有针对性的优化都会是不错的方法.如果你在使用 ...

  4. Docker 私有镜像仓库的搭建及认证

    DockerHub 为我们提供了很多官方镜像和个人上传的镜像,我们可以下载机构或个人提供的镜像,也可以上传我们自己的本地镜像,但缺点是: 由于网络的原因,从 DockerHub 下载和上传镜像速度可能 ...

  5. windows设置定时执行脚本

    如果你写了一些Python程序,想要在特定的时间进行执行,例如你想让一段爬虫程序在每天的上午10点执行一次,那么我们就可以来使用windows自带的定时任务进行设置.由于Windows系统,无法使用L ...

  6. Data Vault玩转数据仓库(三)

    在Data Vault 2.0版本里,其不只是针对数据仓库的建模,同时也包含了架构,方法论以及实现.这篇挑几个概念,附上我个人对其的理解.同时也把这个系列的名字改成<Data Vault玩转数据 ...

  7. iOS 64位静态链接库

    https://www.jianshu.com/p/486e3b737707 https://stackoverflow.com/questions/44635297/setting-an-ios-s ...

  8. Laravel5的验证码功能

    第三方扩展包 mews/captcha 作为基础来实现 Laravel 中的验证码功能 安装 注册 配置验证码文件 前端引用 后端验证 安装前准备(我这边没执行这个,安装成功,但是搜到的文件有写,不清 ...

  9. 0vscode基本插件

    Bracket Pair Colorizer auto-close-tag Auto Rename Tag Bracket Pair Colorizer Dracula ESLint  Code Sp ...

  10. DVWA_sql injection(low)

    这里主要记录下做题时的思路以及步骤,不查看源码也不对源码进行分析.(我这个dvwa是一个在线靶场,所以我也不确定是否与本地靶场有些许出入) 1.Low 将难度调为Low级别后,来到如下界面 首先输入一 ...