1. 请求处理流程 AprEndPoint

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

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

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

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

  • LimitLatch 是连接控制器,它负责控制最大连接数。
  • Acceptor 跑在一个单独的线程中,它在一个死循环里面通过调用 accept() 方法来接收新连接,会返回一个 long 类型的 socket ,然后将这个 socket 封装成 AprSocketWrapper 对象。
  • Poller 本身也跑在一个单独的线程中,它早内部维护了一个 SocketList 对象,这个对象中含有 socket 数组,它在一个死循环里不断检测 socket 的数据就绪状态,一旦有 socket 可读,就生成一个 SocketProcessor 任务对象扔给 Executor 去处理。
  • Executor 就是一个线程池,负责运行 SocketProcessor 任务类, SocketProcessor 的 run() 方法会调用 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 线程调用来注册 PollerEvent 。 Poller 不断的通过内部的 Selector 对象向内核查询 Channel 的状态,一旦可读就生成任务类 SocketProcessor 交给 Executor 去处理。 Poller 的另一个重要任务是循环遍历检查自己所管理的 SocketChannel 是否已经超时,如果有超时就关闭这个 SocketChannel 。
  • Executor 是线程池,负责运行 SocketProcessor 任务类, SocketProcessor 的 run() 方法会调用 Http11Processor 来读取和解析请求数据。 ServerSocketChannel 通过 accept() 接受新的连接, accept() 方法返回获得 SocketChannel 对象,然后将 SocketChannel 对象封装在一个 PollerEvent 对象中,并将 PollerEvent 对象压入 Poller 的 Queue 里,这是个典型的生产者 - 消费者模式, Acceptor 与 Poller 线程之间通过 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. ES6--let,解构赋值,promise && ES7--async

    ES-->IE10.Google.火狐 ES6 let 声明的关键字 不能重复声明 块级作用域 <input type="button" value="1&q ...

  2. android开发之集成zxing,二维码,以及扫描二维码的功能实现。带源代码下载

    package cc.jiusansec.www; import com.google.zxing.WriterException; import com.zxing.activity.Capture ...

  3. js_ts_ec6

    JS.ES.TS三者的关系 https://zhuanlan.zhihu.com/p/148875882 package.json详解 https://www.cnblogs.com/sweet-ic ...

  4. SpringCloud 服务负载均衡和调用 Ribbon、OpenFeign

    1.Ribbon Spring Cloud Ribbon是基于Netflix Ribbon实现的-套客户端―负载均衡的工具. 简单的说,Ribbon是Netlix发布的开源项目,主要功能是提供客户端的 ...

  5. 解读AngularJS的setupModuleLoader函数

    http://www.cnblogs.com/whitewolf/p/angular-module-declare-and-get.html 看了上面这篇文章,自己读了一下代码,以下是个人理解,如有请 ...

  6. 免费获取 IntelliJ IDEA 激活码的 6 种方式!

    你还在满世界找 IntelliJ IDEA 激活码? 破解的不稳定,也是违法的,有安全风险还不一定,不建议大家使用来历不明的补丁. 今天栈长就分享免费获取 IntelliJ IDEA 的 6 种方式, ...

  7. node-vuecli 脚手架安装

    1. vuecli 简介 Vue CLI 致力于将 Vue 生态中的工具基础标准化 Vue CLI 是一个基于 vue.js 进行快速开发的完整系统,是一个全局安装的 npm包,基于webpack构建 ...

  8. 2018.12.30【NOIP提高组】模拟赛C组总结

    2018.12.30[NOIP提高组]模拟赛C组总结 今天成功回归开始做比赛 感觉十分良(zhōng)好(chà). 统计数字(count.pas/c/cpp) 字符串的展开(expand.pas/c ...

  9. oracle之insert语句总结

    insert语句总结 16.1 第一类,insert语句:单行插入 1)SQL> create table a (id int,name char(10) default 'aaa');   / ...

  10. react学习 | 踩坑指南

    react样式模块化的"omit -loader"坑 众所周知 react样式的模块化(css modules) 是自己模块中写自己的css,与其他模块互补影响,解决了命名冲突和全 ...