第一部分:Tomcat系统架构与原理剖析
目录结构
| 目录 | 说明 |
|---|---|
| bin/ | 用于存放可以执行脚本 |
| bin/startup.bat | window用于启动tomcat的脚本 |
| bin/startup.sh | linux平台用于启动tomcat的脚本 |
| bin/shutdown/bat | windows平台用于停止服务的脚本文件 |
| bin/shutdown.sh | linux平台用于停止服务的脚本文件 |
| conf/ | 用于存放tomcat配置文件的目录 |
| conf/logging.properties | 用于配置tomcat日志规则的配置文件 |
| conf/server.xml | 用于配置tomcat应用的配置文件 |
| conf/users.xml | 用于配置tomcat的用户及权限 |
| conf/web.xml | 全局web.xml定义默认配置,在tomcat中web.xml是一个继承关系,我们自定义web.xml文件继承默认web.xml我们可以通过使用覆盖的方式替换默认配置。 |
| lib/ | 用于存放tomcat所依赖第三方jar文件 |
| logs/ | 用于存放tomcat在运行中产生日志文件 |
| webapps/ | 默认发布应用目录 |
| webapps/docs | tomcat说明文档 |
| work/ | 用于存放jsp编译为Servlet过程临时文件,也用于缓存的目录。 |
浏览器访问服务器的流程
绘制一个从用户请求到响应时序图
用户通过浏览器访问服务的一次请求中会经过很多处理过程 第一步:浏览器会与服务器建立TCP连接 第二步:把用户请求的信息封装成http协议所能识别的数据结果 第三步:通过连接发送分装好的http数据到web服务器(Tomcat容器) 第四步:服务器接收到浏览器发送的数据(基于socket连接发送数据) 第五步:服务器需要把接收的数据进行解包,并把用户发送的数据 第六步:服务器需要把解包数据封装为servlet能够使用对象HttpServietRequest和HttpServletResponse对象 第七步:通过请求的url匹配可以处理请求Servlet对象,根据请求方式调用对应处理方法并把封装好的HttpServletRequest和HttpServletResponse对象对应方法 第八步:Servlet执行业务请求,并响应对应信息 第九步:Web容器拿到用户的响应信息分装为HTTP协议识别信息 第十步:通过TPC连接把响应的数据包发送给浏览器 第十一步:浏览器接收Web容器响应数据把数据渲染给用户,完成。
Tomcat系统总体架构
HTTP服务器接收到请求之后把请求交给Servlet容器处理,Servlet容器通过Servlet接口调用业务类。 java定义的Servlet接口和Servlet容器这一套标准叫做Servlet规范 注意: Tomcat是一个实现Servlet规范的要求去实现了Servlet容器,同时它也具有HTTP服务器功能。
Servlet容器的处理流程
Tomcat的总体架构
Tomcat设计了两个核心组件连接器(Connector)和容器(Container)来完成Tomcat的两大核心功能。
连接器 连接器主要复杂对外交流,处理Socket连接,负责网络字节流与Request和Responsede对象的转换。
容器 容器复杂内部处理:加载和管理Servlet以及具体处理Request请求。
Tomcat连接器是怎么设计的?
连接器对 Servlet 容器屏蔽了协议及 I/O 模型等的区别,无论是 HTTP 还是 AJP,在容器中获取到的都是一个标准的 ServletRequest 对象。
Coyote简介
Coyote是Tomcat中连接器组件的名称,是对外的接口。客户端通过Coyote与服务器建立连接、发送请求并接受响应。
(1)Coyote封装了底层网络通信(Socket请求及响应) (2)Coyote使Catalina容器(容器组件)与具体的请求协议及IO操作方式完全解耦。 (3)Coyote将Socket输入转换封装为Request对象,进一步封装后交由Catalina容器处理,处理请求完成后,Catalina在通过Coyote提供Response对象将结果写入输出流。 (4)Coyote复杂具体协议(应用层)和IO(传输层)相关内如。
应用层
传输层
连接器设计
tomcat在设计者在设计连接器时考虑tomcat需要支持不同的网络协议和IO模型如HTTP1.1、HTTP2、ARP所以设计了Endpoint和Processor连个接口,tomcat的设计者在设计连接器时并没有使用Servlet的HttpServletRequest和HttpServletResponse而是设计了一个自己Request和Response对象,为了与Servlet容器交互设计了一个Adapter接口,这个接口主要用于把tomcat的Request和Response对象转化为servlet能使用HttpServletRequest和HttpServletResponse对象。
| 组件 | 描述 |
|---|---|
| Endpoint | 主要用于处理网络通信和IO模型,用于监听端口当收到请求时把请求信息交给具体处理指定协议Processor对象。 |
| Processor | 定义了用于处理指定协议处理器,处理器根据给定socket来解析对应的数据并把数据封装成Request和Response对象在分装为Adapter对象 |
| Adapter | Adapter用于解决Tomcat自己Request和Response对象与HttpServletRequest和HttpServletReponse对象 |
tomcat为了解耦连接器与Servlet的并未使用Servlet的Request和Response对象存储用户请求的信息,所以当Connector调用Servlet容器时不能
| 组件 | 描述 |
|---|---|
| Endpoint | 用于监听端口和处理Socket,当有是请求来的时候把请求交给Processor处理具体请求,当有具体响应的时候处理具体的响应。不同Endpoint使用IO模型。 |
| Processor | 用于解析和封装指定协议如HTTP1.1、HTTP2.0, 当接收请求时候需要根据给定义Socket来解析请求信息并把请求信息封装到一个Request对象中,当收到业务类的响应时需要把响应信息转换为HTTP并输出给用户。 |
| Adapter | 为了能够让Connector可以调用容器service方法,Adapter实现CoyoteRequest与HttpServletRequest对象间的转换操作 |
嵌入式启动Tomcat github.com/heroku/devc…
基于源码启动Tomcat
使用Idea运行Tomcat源码
准备:
- 需要安装java8+以上的jdk
- 需要安装ant中间件tomcat是使用ant管理项目
第一步:首先要下载源码实例使用的是8.5.56版本源码 下载地址:tomcat.apache.org/download-80…
第二步:解压源码并执行ant ide-intellij命令
ant ide-intellij
第三步:新建pom.web文件
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<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>Tomcat8.5</artifactId>
<name>Tomcat8.5</name>
<version>8.5.23</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.10.1</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</groupId>
<artifactId>org.eclipse.jdt.core</artifactId>
<version>3.13.0</version>
</dependency>
</dependencies>
<build>
<finalName>Tomcat8.5</finalName>
<sourceDirectory>java</sourceDirectory>
<testSourceDirectory>test</testSourceDirectory>
<resources>
<resource>
<directory>java</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>test</directory>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
第四步:直接把源码导入idea中
第五步:注释编译失败的测试类中报错的业务逻辑
- TestApplicationContextFacadeSecurityManager
-Dcatalina.home=/Users/zhouyushuai/Desktop/apache-tomcat-8.5.63-src
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=conf/logging.properties
-Duser.language=en
配置VM option参数的截图
-Dcatalina.home=/Users/zhouyushuai/Desktop/apache-tomcat-8.5.63-src
Bootstrap在启动Tomcat时首先会先初始化一个名字叫catalina.home的vm环境变量,tomcat在运行的时候会根据配置目录来查找conf、bin等Tomcat工作所需要的目录,如果不指定Bootstrap会获取源码目录作为工作目录。
static {
// Will always be non-null
String userDir = System.getProperty("user.dir");
// Home first
String home = System.getProperty(Constants.CATALINA_HOME_PROP);
File homeFile = null;
if (home != null) {
File f = new File(home);
try {
homeFile = f.getCanonicalFile();
} catch (IOException ioe) {
homeFile = f.getAbsoluteFile();
}
}
//省略部分代码
System.setProperty(
Constants.CATALINA_BASE_PROP, catalinaBaseFile.getPath());
}
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.util.logging.config.file=conf/logging.properties
用于配置tomcat在启动的时候的日志管理及配置
-Duser.language
用于解决tomcat在启动的时候输入日志乱码的问题
运行Tomcat的配置信息
第六步:访问http://localhost:8080
在访问tomcat时报错,错误信息是不支持jsp。
手动初始化JasperInitializer并设置到context中用于处理jsp
protected synchronized void configureStart() {
// Called from StandardContext.start()
if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.start"));
}
if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.xmlSettings",
context.getName(),
Boolean.valueOf(context.getXmlValidation()),
Boolean.valueOf(context.getXmlNamespaceAware())));
}
webConfig();
//在原有业务逻辑中增加初始化jsp处理器
context.addServletContainerInitializer(new JasperInitializer(), null);
//省略其他业务代码
}
再次访问tomcat就可以正常访问了
现在就可以调试tomcat源码了。
三、Tomcat类加载器
jdk已经实现了完整类加载器了,tomcat为什么还要定义自己的类加载器?
| 系统类加载器 | 描述 |
|---|---|
| BootstrapClassLoader | |
###3.1 双亲委派机制
什么是双亲委派机制?
保证核心class文件不会被篡改,当我们尝试运行一个java应用的时候首先会触发SystemClassloader的运行,SystemClassLoader运行时会把类加载的工作一次委托给父加载器加载。
在tomcat中各类加载器都负责加载那些内容如SystemClassLoader负责加载bin/目录下的bootstrap.jar文件中的class文件。
Tomcat中的WebAppClassLoader加载器打破了双亲委派机制, WebAppClassLoader默认先加载/WEB-INF/classes在加载/WEB-INF/lib然后才调用父级载器。
四、Tomcat配置HTTPS
4.1 基于keystore证书
keytool -genkey -alias tomcat -keyalg RSA -keysize -keystore tomcat.keystore
注意:使用keystore文件类型证书需要使用certificateKeystoreFile和certificateKeystorePassword两个属性来配置证书位置和证书的密码
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true" secure="true" schema="https">
<SSLHostConfig>
<Certificate certificateKeystoreFile="conf/tomcat.keystore"
certificateKeystorePassword="lagou123" keystoreType="RSA"/>
</SSLHostConfig>
</Connector>
4.2 基于jks证书
Tomcat优化
一、JVM调优配置
tomcat优化方向是从tomcat和jvm两个方向优化。
查看jvm使用那个收集器
切换JVM垃圾收集器参数
| 参数 | 描述 |
|---|---|
| -XX:+UseSerialGC | |
| -XX:+UseParallelGC | |
| -XX:+UseParNewGC | |
| -XX:ParallelGCThreads | |
| -XX:+UseConcMarkSweepGC(CMS) | |
| -XX:+UserG1GC | |
java -jar
-XX:+UseG1GC
-Xms128m
-Xmx128m
二、Tomcat配置调优
2.1 线程池配置
配置
<Executor name="tomcatThreadPool"
namePrefix="catalina-exec-"
maxThreads="150"
minSpareThreads="4"/>
线程池参数说明
| 参数 | 说明 |
|---|---|
| maxConnections | 最大连接数,当达到峰值后服务器接收到的新请求不会在被处理。额外的请求将会阻塞直到连接数低于macConnections。可以通过ulimit -a命令查看服务器限制。对于CPU要求更高(计算密集型)的应用,建议不要配置过大;CPU要求不是特别高时,建议配置在2000左右(受服务器性能影响)。需要服务器硬件支持。 |
| maxThreads | 最大线程数,需要根据服务器硬件情况,进行合理配置 |
| acceptCount | 最大排队等待数,当服务器接收的请求达到最大连接数(maxConnectors)时,Tomcat会把新的请求放到任务队列中进行排队。acceptCount指的就是任务队列中排队等待的请数。 |
禁用AJP连接器
调整IO模式
当Tomcat并发性能要求比较高或并发遇到瓶颈时可以考虑使用APR模式,APR(apache portable runtime)是从操作系统级别解决异步IO问题,使用时需要在操作系统上安装APR和navtive库(ARP的原理是使用JNI技术调用系统底层IO接口)
动静分离
可以使用nginx+tomcat相结合的部署方案,nginx负责静态资源、tomcat负责动态资源(tomcat并不擅长处理静态资源,处理静态资源时也会消耗tomcat的资源)。