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. RabbitMQ入门指南

    消息队列(Message Queue,以下简称MQ)常用于异步系统的数据传递.若不用MQ,我们只能[在应用层]使用轮询或接口回调等方式处理,这在效率或耦合度上是难以让人满意的.当然我们也可以在系统间保 ...

  2. Python | 详解Python中的协程,为什么说它的底层是生成器?

    今天是Python专题的第26篇文章,我们来聊聊Python当中的协程. 我们曾经在golang关于goroutine的文章当中简单介绍过协程的概念,我们再来简单review一下.协程又称为是微线程, ...

  3. 基于python tkinter的课堂点名小程序

    import datetime import json import os import random import tkinter as tk import openpyxl # 花名册文件名很多人 ...

  4. Java-学习日记(100 == 100为true,1000 == 1000却为false?)

    Integer底层设计 100 == 100为true,1000 == 1000却为false? 之前也写过String的==与equals的注意点,这次写下Integer的底层设计,不妨先运行下下面 ...

  5. 总结一篇shell调试技巧及常见的脚本错误

      #常见的调试命令工具 1.使用bash命令参数调试 #使用 [root@game ~]# sh [-xvn] test.sh #参数解释: -x:将执行的脚本内容输出出来,可以看到执行的过程 -n ...

  6. let与const认识

      es2015之前 在es2015之前JavaScript并没有块级作用域这个说法,只有全局和函数级的变量,let的出现使JavaScript语言有了块级作用域 在外面访问 { var a=5; l ...

  7. WebApi 接口传参接参

    阅读目录 一.get请求 1.基础类型参数 2.实体作为参数 3.数组作为参数 4.“怪异”的get请求 二.post请求 1.基础类型参数 2.实体作为参数 3.数组作为参数 4.后台发送请求参数的 ...

  8. jQuery 如何实现 模糊搜索

    如何实现 模糊搜索 当我们浏览网页的时候,通常能看到搜索栏,这大大的提高了我们获取数据的目的性.那如何去实现一个简单的模糊搜索 框呢,以下案例获取能给你一点思路. 以下案例,可以实现当按键按下时,自动 ...

  9. DSRC和USRP的购买调研

    (转移自旧博客) 11.29 2019 实验室采购,所以进行了一定程度的调研. 主要包括两个Part,分别是DSRC和USRP的简单总结,购买建议和详细资料. Part.1 DSRC调研总结 1.1 ...

  10. 使用Built-in formatting来创建log字符串

    在一次哦测试中,sonar-qube总是报Use the built-in formatting to contruct this argument, 在网上查了一下,原来它是推荐这样做: log.i ...