20201214 Tomcat

Tomcat 系统架构与原理剖析

Tomcat 系统总体架构

Tomcat 的两重身份:

  • Http 服务器
    • 能够接收并且处理 http 请求
  • Servlet 容器
    • Servlet 接口和 Servlet 容器这⼀整套内容叫作 Servlet 规范
    • Tomcat 实现了 Servlet 规范

Tomcat Servlet容器处理流程

当用户请求某个URL资源时

  1. HTTP 服务器会把请求信息使用 ServletRequest 对象封装起来
  2. 进一步去调用 Servlet 容器中某个具体的 Servlet
  3. 在 2)中, Servlet 容器拿到请求后,根据 URL 和 Servlet 的映射关系,找到相应的 Servlet
  4. 如果 Servlet 还没有被加载,就用反射机制创建这个 Servlet ,并调用 Servlet 的 init 方法来完成初始化
  5. 接着调用这个具体 Servlet 的 service 方法来处理请求,请求处理结果使用 ServletResponse 对象封装
  6. ServletResponse 对象返回给 HTTP 服务器, HTTP 服务器会把响应发送给客户端

img

Tomcat 系统总体架构

Tomcat 设计了两个核心组件连接器( Connector ) 和容器( Container ) 来完成 Tomcat 的两大核心功能。

  • 连接器(Coyote),负责对外交流: 处理 Socket 连接,负责网络字节流与 Request 和 Response 对象的转化;
  • 容器(Catalina),负责内部处理: 加载和管理 Servlet ,以及具体处理 Request 请求;

Tomcat 连接器组件 Coyote

TCP/IP五层协议

img

  • 应用层: HTTP (超文本传输协议), HTTPS (更安全的超文本传输协议), FTP (文件传输协议), SMTP (简单邮件传输协议), DNS (域名服务), ping 命令(调试网络环境), OSPF (开放最短路径优先);
  • 传输层: UDP (用户数据报协议), TCP (传输控制协议);
  • 网络层: IP (因特网协议), ICMP (控制报文协议), ARP (地址解析协议), RARP (反向地址转换协议);

连接器 Coyote

  • Coyote 是 Tomcat 中连接器的组件名称 , 是对外的接口。客户端通过 Coyote 与服务器建立连接、发送请求并接受响应

  • Coyote 封装了底层的网络通信( Socket 请求及响应处理)

  • Coyote 使 Catalina 容器(容器组件)与具体的请求协议及 IO 操作方式完全解耦

  • Coyote 将 Socket 输入转换封装为 Request 对象,进一步封装后交由 Catalina 容器进行处理,处理请求完成后, Catalina 通过 Coyote 提供的 Response 对象将结果写入输出流

  • Coyote 负责的是具体协议(应用层)和 IO (传输层)相关内容

img

Tomcat Coyote 支持的 IO 模型与协议:

img

  • 在 8.0 之前 , Tomcat 默认采用的 I/O 方式为 BIO ,之后改为 NIO 。
  • 论 NIO 、 NIO2 还是 APR , 在性能方面均优于以往的 BIO 。 如果采用 APR , 甚至可以达到 Apache HTTP Server 的影响性能。

Coyote 的内部组件及流程

img

组件 作用描述
EndPoint EndPoint 是 Coyote 通信端点,即通信监听的接口,是具体 Socket 接收和发送处理器,是对传输层的抽象,因此 EndPoint 用来实现 TCP/IP 协议的
Processor Processor 是 Coyote 协议处理接口 ,如果说 EndPoint 是用来实现 TCP/IP 协 议的,那么 Processor 用来实现 HTTP 协议, Processor 接收来自 EndPoint 的 Socket ,读取字节流解析成 Tomcat Request 和 Response 对象,并通过 Adapter 将其提交到容器处理, Processor 是对应用层协议的抽象
ProtocolHandler Coyote 协议接口, 通过 Endpoint 和 Processor , 实现针对具体协议的处理能力。 Tomcat 按照协议和 I/O 提供了 6 个实现类 : AjpNioProtocol , AjpAprProtocol , AjpNio2Protocol , Http11NioProtocol , Http11Nio2Protocol , Http11AprProtocol
Adapter 由于协议不同,客户端发过来的请求信息也不尽相同, Tomcat 定义了自己的 Request 类来封装这些请求信息。 ProtocolHandler 接口负责解析请求并生成 Tomcat Request 类。但是这个 Request 对象不是标准的 ServletRequest ,不能用 Tomcat Request 作为参数来调用容器。
Tomcat 设计者的解决方案是引入 CoyoteAdapter ,这是适配器模式的经典运用,连接器调用 CoyoteAdapter 的 Sevice 方法,传入的是 Tomcat Request 对象, CoyoteAdapter 负责将 Tomcat Request 转成 ServletRequest ,再调用容器

Tomcat Servlet 容器 Catalina

  • Tomcat 是⼀个由一系列可配置(conf/server.xml)的组件构成的 Web 容器,而 Catalina 是 Tomcat 的
    servlet 容器。
  • Catalina 是 Tomcat 的核心 , 其他模块都是为 Catalina 提供支撑的
  • Tomcat 就是一个 Catalina 的实例

Catalina 在容器中的地位:

img

Catalina 的结构:

img

  • 可以认为整个 Tomcat 就是一个 Catalina 实例, Tomcat 启动的时候会初始化这个实例, Catalina 实例通过加载 server.xml 完成其他实例的创建,创建并管理一个 Server , Server 创建并管理多个服务,每个服务又可以有多个 Connector 和一个 Container 。

    • 一个 Catalina 实例(容器)
    • 一个 Server 实例(容器)
    • 多个 Service 实例(容器)
    • 每一个 Service 实例下可以有多个 Connector 实例和一个 Container 实例
  • Catalina

    负责解析 Tomcat 的配置文件( server.xml ) , 以此来创建服务器 Server 组件并进行管理

  • Server

    服务器表示整个 Catalina Servlet 容器以及其它组件,负责组装并启动 Servlet 引擎, Tomcat 连接器。Server 通过实现 Lifecycle 接口,提供了一种优雅的启动和关闭整个系统的方式

  • Service

    服务是 Server 内部的组件,一个 Server 包含多个 Service 。它将若干个 Connector 组件绑定到一个 Container

  • Container

    容器,负责处理用户的 servlet 请求,并返回对象给 web 用户的模块

Container 组件的具体结构

Container 组件下有几种具体的组件,分别是 Engine 、 Host 、 Context 和 Wrapper 。这 4 种组件(容器)是父子关系。 Tomcat 通过一种分层的架构,使得 Servlet 容器具有很好的灵活性。组件的配置其实就体现在conf/server.xml 中。

  • Engine

    表示整个 Catalina 的 Servlet 引擎,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine ,但是一个引擎可包含多个 Host

  • Host

    代表一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可包含多个 Context

  • Context

    表示一个 Web 应用程序, 一个 Web 应用可包含多个 Wrapper

  • Wrapper

    表示一个 Servlet , Wrapper 作为容器中的最底层,不能包含子容器

Tomcat 服务器核心配置详解

  • 配置文件 conf/server.xml
  • Server :Server 实例
    • Listener :监听器
    • GlobalNamingResources :定义服务器的全局 JNDI 资源
    • Service :服务
      • Listener 生命周期监听器
      • Executor 共享线程池
      • Connector 链接器
        • URIEncoding :用于指定编码 URI 的字符编码, Tomcat8.x 版本默认的编码为 UTF-8 , Tomcat7.x 版本默认为 ISO-8859-1
      • Engine 容器引擎
        • Host 虚拟主机
        • Context 用于配置⼀个 Web 应用

手写实现迷你版 Tomcat

Tomcat 源码构建及核心流程源码剖析

源码构建

以 Win64 位,apache-tomcat-8.5.50-src 为例:

  1. 下载源码,解压后,将源码导入 IDEA

  2. 新建 source 目录,将 conf 和 webapps 目录移动到 source 目录下

  3. 在项目根目录下新建 pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
    http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>apache-tomcat-8.5.50-src</artifactId>
        <name>Tomcat8.5</name>
        <version>8.5</version>
        <build>
            <!--指定源⽬录-->
            <finalName>Tomcat8.5</finalName>
            <sourceDirectory>java</sourceDirectory>
            <resources>
                <resource>
                    <directory>java</directory>
                </resource>
            </resources>
            <plugins>
                <!--引⼊编译插件-->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <encoding>UTF-8</encoding>
                        <source>8</source>
                        <target>8</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
        <!--tomcat 依赖的基础包-->
        <dependencies>
            <dependency>
                <groupId>org.easymock</groupId>
                <artifactId>easymock</artifactId>
                <version>3.4</version>
            </dependency>
            <dependency>
                <groupId>ant</groupId>
                <artifactId>ant</artifactId>
                <version>1.7.0</version>
            </dependency>
            <dependency>
                <groupId>wsdl4j</groupId>
                <artifactId>wsdl4j</artifactId>
                <version>1.6.2</version>
            </dependency>
            <dependency>
                <groupId>javax.xml</groupId>
                <artifactId>jaxrpc</artifactId>
                <version>1.1</version>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jdt.core.compiler</groupId>
                <artifactId>ecj</artifactId>
                <version>4.5.1</version>
            </dependency>
            <dependency>
                <groupId>javax.xml.soap</groupId>
                <artifactId>javax.xml.soap-api</artifactId>
                <version>1.4.0</version>
            </dependency>
        </dependencies>
    </project>
    
  4. 为启动类 Bootstrap 添加 VM Options

    -Dcatalina.home=E:DevelopworkspaceLaGouapache-tomcat-8.5.50-srcsource
    -Dcatalina.base=E:DevelopworkspaceLaGouapache-tomcat-8.5.50-srcsource
    -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
    -Djava.util.logging.config.file=E:DevelopworkspaceLaGouapache-tomcat-8.5.50-srcsourceconflogging.properties
    
  5. 增加代码,org.apache.catalina.startup.ContextConfig#configureStart

    webConfig();
    
    // 增加代码,初始化 jasper 引擎
    context.addServletContainerInitializer(new JasperInitializer(), null);
    
  6. 解决日志中文乱码问题

    1. org.apache.jasper.compiler.Localizer#getMessage(java.lang.String)
    	errMsg = new String(errMsg.getBytes("ISO-8859-1"), "UTF8");
    
    2. org.apache.tomcat.util.res.StringManager#getString(java.lang.String)
    	str = new String(str.getBytes("ISO-8859-1"), "UTF8");
    

核心流程源码剖析

  • Tomcat 中的各容器组件都会涉及创建、销毁等,因此设计了生命周期接口 Lifecycle 进行统一规范,各容器组件实现该接口
    • org.apache.catalina.Lifecycle
  • 关注两个流程: Tomcat 启动流程和 Tomcat 请求处理流程

Tomcat 启动流程

img

Tomcat 请求处理流程

img

Mapper 组件体系结构

img

Tomcat 类加载机制剖析

JVM 的类加载机制

  • JVM 内置了几种类加载器
    • 引导类加载器
    • 扩展类加载器
    • 系统类加载器
类加载器 作用
引导启动类加载器 BootstrapClassLoader c++ 编写,加载 java 核心库 java.*,比如 rt.jar 中的类,构 造 ExtClassLoaderAppClassLoader
扩展类加载器 ExtClassLoader java 编写,加载扩展库 JAVA_HOME/lib/ext 目录下的 jar 中的类,如 classpath 中的 jre , javax.* 或者 java.ext.dir 指定位置中的类
系统类加载器 AppClassLoader 默认的类加载器,搜索环境变量 classpath 中指明的路径
  • 用户可以自定义类加载器( Java 编写,用户自定义的类加载器,可加载指定路径的 class 文件)

当 JVM 运行过程中,用户自定义了类加载器去加载某些类时,会按照下面的步骤(父类委托机制)

  1. 用户自己的类加载器,把加载请求传给父加载器,父加载器再传给其父加载器,一直到加载器树的顶层
  2. 最顶层的类加载器首先针对其特定的位置加载,如果加载不到就转交给子类
  3. 如果一直到底层的类加载都没有加载到,那么就会抛出异常 ClassNotFoundException

因此,按照这个过程可以想到,如果同样在 classpath 指定的目录中和自己⼯作目录中存放相同的 class ,会优先加载 classpath 目录中的文件

双亲委派机制

  • 当某个类加载器需要加载某个 .class 文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类

Tomcat 的类加载机制

  • Tomcat 的类加载机制没有严格的遵从双亲委派机制,也可以说打破了双亲委派机制

img

  • 引导类加载器 和 扩展类加载器 的作用不变

  • 系统类加载器正常情况下加载的是 CLASSPATH 下的类,但是 Tomcat 的启动脚本并未使用该变量,而是加载 tomcat 启动的类,比如 bootstrap.jar ,通常在 catalina.bat 或者 catalina.sh 中指定。位于 CATALINA_HOME/bin

  • Common 通用类加载器加载 Tomcat 使用以及应用通用的一些类,位于 CATALINA_HOME/lib 下,比如 servlet - api.jar

  • Catalina ClassLoader 用于加载服务器内部可见类,这些类应用程序不能访问

  • Shared ClassLoader 用于加载应用程序共享类,这些类服务器不会依赖

  • Webapp ClassLoader ,每个应用程序都会有一个独一无二的 Webapp ClassLoader ,他用来加载本应用程序 /WEB-INF/classes/WEB-INF/lib 下的类。

Tomcat 8.5 默认改变了严格的双亲委派机制:

  • 首先从 Bootstrap Classloader加载指定的类
  • 如果未加载到,则从 /WEB-INF/classes 加载
  • 如果未加载到,则从 /WEB-INF/lib/*.jar 加载
  • 如果未加载到,则依次从 System、 Common、 Shared 加载(在这最后⼀步,遵从双亲委派机制)

Tomcat 对 Https 的支持及 Tomcat 性能优化策略

Tomcat 对 HTTPS 的支持

  • HTTPS(Hyper Text Transfer Protocol over SecureSocket Layer)

  • SSL(Secure Sockets Layer 安全套接字协议)

HTTPS 和 HTTP 的主要区别:

  • HTTPS 协议使用时需要到电子商务认证授权机构( CA )申请 SSL 证书
  • HTTP 默认使用 8080 端口, HTTPS 默认使用 8443 端口
  • HTTPS 则是具有 SSL 加密的安全性传输协议,对数据的传输进行加密,效果上相当于 HTTP 的升级版
  • HTTP 的连接是无状态的,不安全的; HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全

HTTPS 工作原理

img

Tomcat 对 HTTPS 的支持

  1. 生成证书 lagou.keystore
keytool -genkey -alias lagou -keyalg RSA -keystore lagou.keystore
  1. 配置 conf/server.xml
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="150" schema="https" secure="true" SSLEnabled="true">
    <SSLHostConfig>
        <Certificate certificateKeystoreFile="E:Develop	omcat8.5.50apache-tomcat-8.5.50conflagou.keystore" certificateKeystorePassword="123456" type="RSA" />
    </SSLHostConfig>
</Connector>
  1. 启动 tomcat,访问 https://localhost:8443/

Tomcat 性能优化策略

系统性能的衡量指标,主要是响应时间和吞吐量:

  1. 响应时间:执行某个操作的耗时;
  2. 吞吐量:系统在给定时间内能够支持的事务数量,单位为 TPS(Transactions PerSecond的缩写,也就是事务数/秒,⼀个事务是指⼀个客户机向服务器发送请求然后服务器做出反应的过程。

Tomcat 优化从两个方面进行:

  1. JVM 虚拟机优化(优化内存模型)
  2. Tomcat 自身配置的优化(比如是否使用了共享线程池? IO 模型?)

Tomcat 配置调优

  • 调整 Tomcat 线程池
    • 使用 Executor 标签
  • 调整 Tomcat 的连接器
    • Connector 标签
参数 说明
maxThreads 最大线程数,需要根据服务器的硬件情况,进行一个合理的设置
maxConnections 最大连接数,当到达该值后,服务器接收但不会处理更多的请求, 额外的请求将会阻塞直到连接数低于 maxConnections 。可通过 ulimit -a 查看服务器连接数限制。对于 CPU 要求更高(计算密集型)时,建议不要配置过大 ; 对于 CPU 要求 不是特别高时,建议配置在 2000 左右(受服务器性能影响)。 当然这个需要服务器硬件的支持
acceptCount 最大排队等待数,当服务器接收的请求数量到达 maxConnections ,此时 Tomcat 会将后面的请求,存放在任务队列中进行排序, acceptCount 指的 就是任务队列中排队等待的请求数 。
一台 Tomcat 的最大的请求处理数量, 是 maxConnections+acceptCount
  • 禁用 AJP 连接器
  • 调整 IO 模式
    • Tomcat8 之前的版本默认使用 BIO (阻塞式 IO ),对于每一个请求都要创建一个线程来处理,不适合高并发; Tomcat8 以后的版本默认使用 NIO 模式(非阻塞式 IO )
    • 当 Tomcat 并发性能有较高要求或者出现瓶颈时,我们可以尝试使用 APR 模式, APR ( Apache PortableRuntime )是从操作系统级别解决异步 IO 问题,使用时需要在操作系统上安装 APR 和 Native (因为 APR 原理是使用使用 JNI 技术调用操作系统底层的 IO 接口)
  • 动静分离
    • 可以使用 Nginx+Tomcat 相结合的部署方案, Nginx 负责静态资源访问, Tomcat 负责 Jsp 等动态资源访问处理(因为 Tomcat 不擅长处理静态资源)。

参考资料

原文地址:https://www.cnblogs.com/huangwenjie/p/14135847.html