<返回更多

Tomcat深入解析与性能优化

2020-11-11    
加入收藏
Tomcat深入解析与性能优化

 

前言

 

JAVA 应用中,常用的 Web 服务器一般由 Tomcat、weblogic、jetty、undertwo等。但从 Java 2019和2020 生态使用报告可以看到,tomcat的用户量对比明显较大,当然这也基于它开源和免费的特点。

 

Java 2019 年生态圈使用报告

Tomcat深入解析与性能优化

 

2020 Java 生态系统报告

Tomcat深入解析与性能优化

 

从软件架构的发展角度来看,软件架构大致经历了如下几个阶段:

Tomcat深入解析与性能优化

 

从 Java Web 角度来说,架构大致经历了:

Tomcat深入解析与性能优化

 

从当前企业使用的架构角度来说,使用SSM架构项目比较多,SSH基本被淘汰(大部分是老项目维护),很大一部分企业转向微服务架构了。

 

基于Spring 生态来说,大部分中小型企业都基本使用SpringBoot,SpringBoot本身集成了 tomcat、jetty和undertwo 容器,那么我们为什么需要花时间来研究tomcat呢?

  1. 当前tomcat依然是主流java web容器,研究它符合java 技术生态发展;
  2. 在java web项目调优中,如ssm项目中,在优化项目时,jvm和tomcat同样重要,都需要优化;
  3. 尽管springboot内置了tomcat容器,且配置了默认的tomcat参数,但当默认的tomcat参数满足不了项目优化要求时,就需要优化人员手动进行相关的参数优化,因此研究tomcat非常必要;
  4. 熟悉tomcat架构,是后续进行项目优化的基础,也是必备条件。

Tomcat架构说明


知识点:

 

  1. Tomcat目录结构
  2. Tomcat简要架构
  3. Tomcat各组件及关系
  4. Tomcat server.xml配置详解
  5. Tomcat启动参数说明(启动脚本)

 

Tomcat 是一个基于JAVA的WEB容器,其实现了JAVA EE中的 Servlet 与 jsp 规范,与Nginx Apache 服务器不同在于一般用于动态请求处理。在架构设计上采用面向组件的方式设计。即整体功能是通过组件的方式拼装完成。另外每个组件都可以被替换以保证灵活性。

Tomcat深入解析与性能优化

 

通过Tomcat官方可以看到,目前已经更新到Tomcat 10了,但当前大部分企业使用的Tomcat 为8或者9版本。

Tomcat深入解析与性能优化

 

Tomcat 目录结构

Tomcat深入解析与性能优化

 

bin目录

 

bin目录存放可执行文件,简要结束常用命令

Tomcat深入解析与性能优化

 

这里主要解释如下通用的命令,其他命令就不一一介绍

 

conf目录

 

conf文件夹用来存放tomcat相关配置文件

Tomcat深入解析与性能优化

 

1.catalina.policy

 

项目安全文件,用来防止欺骗代码或JSP执行带有像System.exit(0)这样的命令的可能影响容器的破坏性代码. 只有当Tomcat用-security命令行参数启动时这个文件才会被使用,即启动tomcat时, startup.sh -security 。

Tomcat深入解析与性能优化

 

上图中,tomcat容器下部署两个项目,项目1和项目2。由于项目1中有代码System.exit(0),当访问该代码时,该代码会导致整个tomcat停止,从而也导致项目2停止。

 

为了解决因项目1存在欺骗代码或不安全代码导致损害Tomcat容器,从而影响其他项目正常运行的问题,启动tomcat容器时,加上-security参数就,即startup.sh -security,如此即使项目1中有代码System.exit(0),也只会仅仅停止项目1,而不会影响Tomcat容器,然而起作用的配置文件就是catalina.policy文件。

 

2.catalina.properties

 

配置tomcat启动相关信息文件

 

3.context.xml

 

监视并加载资源文件,当监视的文件发生发生变化时,自动加载

Tomcat深入解析与性能优化

 

 

4.jaspic-providers.xml 和 jaspic-providers.xsd

 

这两个文件不常用

 

5.logging.properties

 

该文件为tomcat日志文件,包括配置tomcat输出格式,日志级别等

 

6.server.xml

 

tomcat核心架构主件文件,下面会详细解析。

 

7.tomcat-users.xml和tomcat-users.xsd

 

tomcat用户文件,如配置远程登陆账号

 

tomcat-users.xsd 为tomcat-users.xml描述和约束文件

 

8.web.xml

 

tomcat全局配置文件。

 

lib目录

 

lib文件夹主要用来存放tomcat依赖jar包,如下为 tomcat 的lib文件夹下的相关jar包。

Tomcat深入解析与性能优化

 

每个jar包功能,这里就不讲解了,这里主要分析ecj-4.13.jar,这个jar包起到将.java编译成.class字节码作用。

 

假设要编译MyTest.java,那么jdk会执行两步:

javac MyTest.java

java MyTest.class

java -jar ecj-4.13.jar MyTest.java

 

logs目录

 

该文件夹表示tomcat日志文件,大致包括如下六类文件:

Tomcat深入解析与性能优化

 


Tomcat深入解析与性能优化

 

temp目录

 

temp目录用户存放tomcat在运行过程中产生的临时文件。(清空不会对tomcat运行带来影响)。

 

webapps目录

 

webapps目录用来存放应用程序,当tomcat启动时会去加载webapps目录下的应用程序。可以以文件夹、war包、jar包的形式发布应用。

当然,你也可以把应用程序放置在磁盘的任意位置,在配置文件中映射好就行。

 

work目录

 

work目录用来存放tomcat在运行时的编译后文件,例如JSP编译后的文件。

清空work目录,然后重启tomcat,可以达到清除缓存的作用。

 

Tomcat 简要架构

 

Tomcat深入解析与性能优化

 

Tomcat 各组件及关系

 

Tomcat深入解析与性能优化

 

Tomcat server.xml 配置详解


Server 的基本基本配置:

<Server>
    <Listener /><!-- 监听器 -->
    <GlobaNamingResources> <!-- 全局资源 -->
    </GlobaNamingResources
    <Service>          <!-- 服务 用于 绑定 连接器与 Engine -->
        <Connector 8080/> <!-- 连接器-->
        <Connector 8010 /> <!-- 连接器-->
        <Connector 8030/> <!-- 连接器-->
        
        <Engine>      <!-- 执行引擎-->
            <Logger />
            <Realm />
               <host "www.test.com" appBase="">  <!-- 虚拟主机-->
                   <Logger /> <!-- 日志配置-->
                   <Context "/applction" path=""/> <!-- 上下文配置-->
               </host>
        </Engine>
    </Service>
</Server>

 

server

root元素:server 的顶级配置

主要属性:

port:执行关闭命令的端口号

shutdown:关闭命令

 

#基于telent 执行SHUTDOWN 命令即可关闭
telent 127.0.0.1 8005
SHUTDOWN

 

service

 

服务:将多个connector 与一个Engine组合成一个服务,可以配置多个服务。

 

Connector

 

连接器:用于接收 指定协议下的连接 并指定给唯一的Engine 进行处理。

 

主要属性:

 

 <Connector port="8860" protocol="org.apache.coyote.http11.Http11NioProtocol"
                connectionTimeout="20000"
                redirectPort="8862"
                URIEncoding="UTF-8"
                useBodyEncodingForURI="true"
                compression="on" compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/plain,text/JavaScript,text/css,application/x-json,application/json,application/x-javascript"
                maxThreads="1024" minSpareThreads="200"
                acceptCount="800"
                enableLookups="false"
        />

 

Engine

 

引擎:用于处理连接的执行器,默认的引擎是catalina。一个service 中只能配置一个Engine。

主要属性:name 引擎名称 defaultHost 默认host

 

Host

虚拟机:基于域名匹配至指定虚拟机。类似于nginx 当中的server,默认的虚拟机是localhost.

<Host name="www.test.com"  appBase="/usr/www/test"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"               prefix="www.luban.com.access_log" suffix=".txt"
               pattern="%h %l %u %t "%r" %s %b" />
</Host>

 

Context

 

应用上下文:一个host 下可以配置多个Context ,每个Context 都有其独立的classPath。相互隔离,以免造成ClassPath 冲突。

 

<Context docBase="hello" path="/h" reloadable="true"/>

 

Valve

 

阀门:可以理解成request 的过滤器,具体配置要基于具体的Valve 接口的子类。以下即为一个访问日志的Valve.

 

 <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="www.luban.com.access_log" suffix=".txt"
               pattern="%h %l %u %t "%r" %s %b" />

 

Tomcat启动参数说明

 

我们平时启动Tomcat过程是怎么样的?

  1. 复制WAR包至Tomcat webapp 目录。
  2. 执行starut.bat 脚本启动。
  3. 启动过程中war 包会被自动解压装载。

但是我们在Eclipse 或idea 中启动WEB项目的时候 也是把War包复杂至webapps 目录解压吗?显然不是,其真正做法是在Tomcat程序文件之外创建了一个部署目录,在一般生产环境中也是这么做的 即:Tomcat 程序目录和部署目录分开 。

我们只需要在启动时指定CATALINA_HOME 与 CATALINA_BASE 参数即可实现。

Tomcat深入解析与性能优化

 

可以编写一个脚本 来实现自定义配置:

更新 启动 脚本

#!/bin/bash -e
export now_time=$(date +%Y-%m-%d_%H-%M-%S)
echo "deploy time:$now_time"

app=$1
version=$2
mkdir -p war/
#从svn下载程序至 war目录
war=war/${app}_${version}.war
echo "$war"
svn export svn://192.168.0.253/release/${app}_${version}.war $war

deploy_war() {
#解压版本至当前目录
target_dir=war/${app}_${version}_${now_time}
unzip -q $war -d $target_dir
rm -f appwar
ln -sf $target_dir appwar
target_ln=`pwd`/appwar
echo '<?xml version="1.0" encoding="UTF-8" ?>
<Context docBase="'$target_ln'" allowLinking="false">
</Context>' > conf/Catalina/localhost/ROOT.xml
#重启Tomcat服务
./tomcat.sh restart
}

deploy_war
```

 

自动部署脚本

#!/bin/bash -e
export now_time=$(date +%Y-%m-%d_%H-%M-%S)
echo "deploy time:$now_time"

app=$1
version=$2
mkdir -p war/
#从svn下载程序至 war目录
war=war/${app}_${version}.war
echo "$war"
svn export svn://192.168.0.253/release/${app}_${version}.war $war

deploy_war() {
#解压版本至当前目录
target_dir=war/${app}_${version}_${now_time}
unzip -q $war -d $target_dir
rm -f appwar
ln -sf $target_dir appwar
target_ln=`pwd`/appwar
echo '<?xml version="1.0" encoding="UTF-8" ?>
<Context docBase="'$target_ln'" allowLinking="false">
</Context>' > conf/Catalina/localhost/ROOT.xml
#重启Tomcat服务
./tomcat.sh restart
}

deploy_war
```

 

Tomcat 网络通信模型剖析


Tomcat 支持四种线程模型介绍

 

什么是IO?

IO是指为数据传输所提供的输入输出流,其输入输出对象可以是:文件、网络服务、内存等。

Tomcat深入解析与性能优化

 

什么是IO模型?

 

提问:

假设应用在从硬盘中读取一个大文件过程中,此时CPU会与硬盘一样处于高负荷状态么?

 

演示:

 

演示结果:CPU 没有太高的增长

 

通常情况下IO操作是比较耗时的,所以为了高效的使用硬件,应用程序可以用一个专门线程进行IO操作,而另外一个线程则利用CPU的空闲去做其它计算。这种为提高应用执行效率而采用的IO操作方法即为IO模型。

 

各IO模型简要说明

BIO

阻塞式IO,即Tomcat使用传统的java.io进行操作。该模式下每个请求都会创建一个线程,对性能开销大,不适合高并发场景。优点是稳定,适合连接数目小且固定架构。

NIO

非阻塞式IO,jdk1.4 之后实现的新IO。该模式基于多路复用选择器监测连接状态在通知线程处理,从而达到非阻塞的目的。比传统BIO能更好的支持并发性能。Tomcat 8.0之后默认采用该模式

APR

全称是 Apache Portable Runtime/Apache可移植运行库),是Apache HTTP服务器的支持库。可以简单地理解为,Tomcat将以JNI的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作。使用需要编译安装APR 库

AIO

异步非阻塞式IO,jdk1.7后之支持 。与nio不同在于不需要多路复用选择器,而是请求处理线程执行完成进行回调调制,已继续执行后续操作。Tomcat 8之后支持。

Tomcat深入解析与性能优化

 

使用指定IO模型的配置方式:

 

配置 server.xml 文件当中的 <Connector protocol="HTTP/1.1"> 修改即可。

默认配置 8.0 protocol=“HTTP/1.1” 8.0 之前是 BIO, 8.0 之后是 NIO

 

protocol=“org.apache.coyote.http11.Http11Protocol”

protocol=“org.apache.coyote.http11.Http11NioProtocol”

protocol=“org.apache.coyote.http11.Http11Nio2Protocol”

protocol=“org.apache.coyote.http11.Http11AprProtocol”

 

Tomcat BIO、NIO实现过程源码解析

 

BIO 与NIO区别

分别演示在高并发场景下BIO与NIO的线程数的变化?

Tomcat深入解析与性能优化

 

BIO 配置

<Connector port="8080" protocol="org.apache.coyote.http11.Http11Protocol"
               connectionTimeout="20000"
               redirectPort="8443"
               compression="on" compressionMinSize="1024"
               compressableMimeType="text/html,text/xml,text/plain,text/javascript,text/css,application/x-json,application/json,application/x-javascript"
               maxThreads="500" minSpareThreads="1"/>

 

NIO配置

 <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
               connectionTimeout="20000"
               redirectPort="8443"
               compression="on" compressionMinSize="1024"
               compressableMimeType="text/html,text/xml,text/plain,text/javascript,text/css,application/x-json,application/json,application/x-javascript"
               maxThreads="500" minSpareThreads="1"/>

 

演示数据:

Tomcat深入解析与性能优化

 

生成环境重要因素:

  1. 网络
  2. 程序执行业务用时

 

源代码地址:https://github.com/org-hejianhui/bit-bigdata-transmission

 

BIO 线程模型

Tomcat深入解析与性能优化

 

BIO 源码

 

线程组:

Accept 线程组 acceptorThreadCount 默认1个

exec 线程组 maxThread

JIoEndpoint

Acceptor extends Runnable

SocketProcessor extends Runnable

Tomcat深入解析与性能优化

 

NIO 线程模型

Tomcat深入解析与性能优化

 

NIO 线程模型

 

Accept 线程组 默认两个轮询器

Poller Selector PollerEvent轮询线程状态

SocketProcessor

Tomcat深入解析与性能优化

 

 

BIO

线程数量 会受到 客户端阻塞、网络延迟、业务处理慢===>线程数会更多。

 

NIO

线程数量 会受到业务处理慢===>线程数会更多。

 

Tomcat connector 并发参数解读

 

Tomcat深入解析与性能优化

 

Tomcat 类加载机制源码解析

 

类加载的本质

 

是用来加载 Class 的。它负责将 Class 的字节码形式转换成内存形式的 Class 对象。字节码可以来自于磁盘文件 .class,也可以是 jar 包里的 .class,也可以来自远程服务器提供的字节流,字节码的本质就是一个字节数组 []byte,它有特定的复杂的内部格式。

 

JVM 运行实例中会存在多个 ClassLoader,不同的 ClassLoader 会从不同的地方加载字节码文件。它可以从不同的文件目录加载,也可以从不同的 jar 文件中加载,也可以从网络上不同的静态文件服务器来下载字节码再加载。

 

jvm里ClassLoader的层次结构

Tomcat深入解析与性能优化

 

类加载器层次结构

 

BootstrapClassLoader(启动类加载器)

 

称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等,可通过如下程序获得该类加载器从哪些地方加载了相关的jar或class文件:

URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL url : urLs) {
    System.out.println(url.toExternalForm());
}

程序执行结果如下:

file:/Library/Java/JavaVirtualmachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/classes

从rt.jar中选择String类,看一下String类的类加载器是什么

ClassLoader classLoader = String.class.getClassLoader();
System.out.println(classLoader);

执行结果如下:

null

 

可知由于BootstrapClassLoader对Java不可见,所以返回了null,我们也可以通过某一个类的加载器是否为null来作为判断该类是不是使用BootstrapClassLoader进行加载的依据。

 

ExtensionClassLoader

 

ExtClassLoader称为扩展类加载器,主要负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目录下的所有jar包或者由java.ext.dirs系统属性指定的jar包.放入这个目录下的jar包对AppClassLoader加载器都是可见的(因为ExtClassLoader是AppClassLoader的父加载器,并且Java类加载器采用了委托机制)。

 

ExtClassLoader的类扫描路径通过执行下面代码来看一下:

String extDirs = System.getProperty("java.ext.dirs");
for (String path : extDirs.split(";")) {
System.out.println(path);
}

执行结果如下(Mac系统):

/Users/hjh/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java

jre/lib/ext路径下内容为:

Tomcat深入解析与性能优化

 

从上面的路径中随意选择一个类,来看看它的类加载器是什么:

sun.misc.Launcher$ExtClassLoader@4439f31e
null

从上面的程序运行结果可知ExtClassLoader的父加载器为null,之前说过BootstrapClassLoader对Java不可见,所以返回了null。ExtClassLoader的父加载器返回的是null,那是否说明ExtClassLoader的父加载器是BootstrapClassLoader?

 

Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用,JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载。然后呢,我们前面已经分析了,JVM初始化sun.misc.Launcher并创建Extension ClassLoader和AppClassLoader实例。并将ExtClassLoader设置为AppClassLoader的父加载器。Bootstrap没有父加载器,但是它却可以作用一个ClassLoader的父加载器。比如ExtClassLoader。这也可以解释之前通过ExtClassLoader的getParent方法获取为Null的现象

 

AppClassLoader

才是直接面向我们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。

 

加载System.getProperty("java.class.path")所指定的路径或jar。在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classes HelloWorld

 

public class AppClassLoaderTest {
    public static void main(String[] args) {
        System.out.println(ClassLoader.getSystemClassLoader());
    }
}

 

输出结果如下:

sun.misc.Launcher$AppClassLoader@18b4aac2

 

以上结论说明调用ClassLoader.getSystemClassLoader()可以获得AppClassLoader类加载器。

protected ClassLoader() {
    this(checkCreateClassLoader(), getSystemClassLoader());
}

通过查看ClassLoader的源码发现并且在没有特定说明的情况下,用户自定义的任何类加载器都将该类加载器作为自定义类加载器的父加载器。

 

通过执行上面的代码即可获得classpath的加载路径。

 

在上面的main函数的类的加载就是使用AppClassLoader加载器进行加载的,可以通过执行下面的代码得出这个结论

public class AppClassLoaderTest {

    public static void main(String[] args) {
        ClassLoader classLoader = Test.class.getClassLoader();
        System.out.println(classLoader);
        System.out.println(classLoader.getParent());
    }

    private static class Test {

    }

}

 

执行结果如下:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@2d209079

从上面的运行结果可以得知AppClassLoader的父加载器是ExtClassLoader

 

Tomcat的 类加载顺序

 

Tomcat深入解析与性能优化

 

在Tomcat中,默认的行为是先尝试在Bootstrap和Extension中进行类型加载,如果加载不到则在Webapp ClassLoader中进行加载,如果还是找不到则在Common中进行查找。

 

NoClassDefFoundError

 

NoClassDefFoundError是在开发JavaEE程序中常见的一种问题。该问题会随着你所使用的JavaEE中间件环境的复杂度以及应用本身的体量变得更加复杂,尤其是现在的JavaEE服务器具有大量的类加载器。

在JavaDoc中对NoClassDefFoundError的产生是由于JVM或者类加载器实例尝试加载类型的定义,但是该定义却没有找到,影响了执行路径。换句话说,在编译时这个类是能够被找到的,但是在执行时却没有找到。

这一刻IDE是没有出错提醒的,但是在运行时却出现了错误。

 

NoSuchMethodError

 

在另一个场景中,我们可能遇到了另一个错误,也就是NoSuchMethodError。

NoSuchMethodError代表这个类型确实存在,但是一个不正确的版本被加载了。

 

ClassCastException

 

ClassCastException,在一个类加载器的情况下,一般出现这种错误都会是在转型操作时,比如:A a = (A) method();,很容易判断出来method()方法返回的类型不是类型A,但是在 JavaEE 多个类加载器的环境下就会出现一些难以定位的情况。

 

部分图片来源于网络,版权归原作者,侵删。
声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>