Tomcat 系统架构与原理剖析
浏览器访问服务器的流程
浏览器访问服务器使⽤的是Http协议,Http是应⽤层协议,⽤于定义数据通信的格式,具体的数
据传输使⽤的是TCP/IP协议
Tomcat 系统总体架构
Tomcat 请求处理⼤致过程
Tomcat是⼀个Http服务器(能够接收并且处理http请求,所以tomcat是⼀个http服务器)
我们使⽤浏览器向某⼀个⽹站发起请求,发出的是Http请求,那么在远程,Http服务器接收到这个请求
之后,会调⽤具体的程序(Java类)进⾏处理,往往不同的请求由不同的Java类完成处理。
HTTP 服务器接收到请求之后把请求交给Servlet容器来处理,Servlet 容器通过Servlet接⼝调⽤业务
类。Servlet接⼝和Servlet容器这⼀整套内容叫作Servlet规范。
注意:Tomcat既按照Servlet规范的要求去实现了Servlet容器,同时它也具有HTTP服务器的功能。
以上我们可以得出:Tomcat的两个重要身份
1)http服务器
2)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服务器会把响应发送给客户端
Tomcat 系统总体架构
通过上⾯的讲解,我们发现tomcat有两个⾮常重要的功能需要完成
1)和客户端浏览器进⾏交互,进⾏socket通信,将字节流和Request/Response等对象进⾏转换
2)Servlet容器处理业务逻辑
Tomcat 设计了两个核⼼组件连接器(Connector)和容器(Container)来完成 Tomcat 的两⼤核⼼
功能。
连接器,负责对外交流: 处理Socket连接,负责⽹络字节流与Request和Response对象的转化;
容器,负责内部处理:加载和管理Servlet,以及具体处理Request请求
Tomcat 连接器组件 Coyote
Coyote 简介
Coyote 是Tomcat 中连接器的组件名称 , 是对外的接⼝。客户端通过Coyote与服务器建⽴连接、发送请
求并接受响应 。
(1)Coyote 封装了底层的⽹络通信(Socket 请求及响应处理)
(2)Coyote 使Catalina 容器(容器组件)与具体的请求协议及IO操作⽅式完全解耦
(3)Coyote 将Socket 输⼊转换封装为 Request 对象,进⼀步封装后交由Catalina 容器进⾏处理,处
理请求完成后, Catalina 通过Coyote 提供的Response 对象将结果写⼊输出流
(4)Coyote 负责的是具体协议(应⽤层)和IO(传输层)相关内容
在 8.0 之前 ,Tomcat 默认采⽤的I/O⽅式为 BIO,之后改为 NIO。 ⽆论 NIO、NIO2 还是 APR, 在性
能⽅⾯均优于以往的BIO。 如果采⽤APR, 甚⾄可以达到 Apache HTTP Server 的影响性能。
Coyote 的内部组件及流程
Tomcat Servlet 容器 Catalina
Tomcat 模块分层结构图及Catalina位置
Tomcat是⼀个由⼀系列可配置(conf/server.xml)的组件构成的Web容器,⽽Catalina是Tomcat的
servlet容器。
从另⼀个⻆度来说,Tomcat 本质上就是⼀款 Servlet 容器, 因为 Catalina 才是 Tomcat 的核⼼ , 其
他模块都是为Catalina 提供⽀撑的。 ⽐如 : 通过 Coyote 模块提供链接通信,Jasper 模块提供 JSP 引
擎,Naming 提供JNDI 服务,Juli 提供⽇志服务。
Servlet 容器 Catalina 的结构
Tomcat(我们往往有⼀个认识,Tomcat就是⼀个Catalina的实例,因为Catalina是Tomcat的核⼼)
Tomcat/Catalina实例
其实,可以认为整个Tomcat就是⼀个Catalina实例,Tomcat 启动的时候会初始化这个实例,Catalina
实例通过加载server.xml完成其他实例的创建,创建并管理⼀个Server,Server创建并管理多个服务service,
每个服务service⼜可以有多个Connector和⼀个Container。
⼀个Catalina实例(容器)
⼀个 Server实例(容器)
多个Service实例(容器)
每⼀个Service实例下可以有多个Connector实例和⼀个Container实例
Catalina
负责解析Tomcat的配置⽂件(server.xml) , 以此来创建服务器Server组件并进⾏管理
Server
服务器表示整个Catalina Servlet容器以及其它组件,负责组装并启动Servlaet引擎,Tomcat连接器。Server通过实现Lifecycle接⼝,提供了⼀种优雅的启动和关闭整个系统的⽅式
Service
服务是Server内部的组件,⼀个Server包含多个Service。它将若⼲个Connector组件绑定到⼀个Container
Container
容器,负责处理⽤户的servlet请求,并返回对象给web⽤户的模块
Container 组件的具体结构
Container组件下有⼏种具体的组件,分别是Engine、Host、Context和Wrapper。这4种组件(容器)是⽗⼦关系。Tomcat通过⼀种分层的架构,使得Servlet容器具有很好的灵活性。
Tomcat 服务器核⼼配置详解
问题⼀:去哪⼉配置? 核⼼配置在tomcat⽬录下conf/server.xml⽂件
问题⼆:怎么配置?
注意:
Tomcat 作为服务器的配置,主要是 server.xml ⽂件的配置;
server.xml中包含了 Servlet容器的相关配置,即 Catalina 的配置;
xml ⽂件的讲解主要是标签的使⽤
server.xml 源码
<?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.
-->
<!-- Note: A "Server" is not itself a "Container", so you may not
define subcomponents such as "Valves" at this level.
Documentation at /docs/config/server.html
-->
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- Security listener. Documentation at /docs/config/listeners.html
<Listener className="org.apache.catalina.security.SecurityListener" />
-->
<!--APR library loader. Documentation at /docs/apr.html -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<!-- Prevent memory leaks due to use of particular java/javax APIs-->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<!-- Global JNDI resources
Documentation at /docs/jndi-resources-howto.html
-->
<GlobalNamingResources>
<!-- Editable user database that can also be used by
UserDatabaseRealm to authenticate users
-->
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<!-- A "Service" is a collection of one or more "Connectors" that share
a single "Container" Note: A "Service" is not itself a "Container",
so you may not define subcomponents such as "Valves" at this level.
Documentation at /docs/config/service.html
-->
<Service name="Catalina">
<!--The connectors can use a shared executor, you can define one or more named thread pools-->
<!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->
<!-- A "Connector" represents an endpoint by which requests are received
and responses are returned. Documentation at :
Java HTTP Connector: /docs/config/http.html
Java AJP Connector: /docs/config/ajp.html
APR (HTTP/AJP) Connector: /docs/apr.html
Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!-- A "Connector" using the shared thread pool-->
<!--
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
-->
<!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443
This connector uses the NIO implementation. The default
SSLImplementation will depend on the presence of the APR/native
library and the useOpenSSL attribute of the
AprLifecycleListener.
Either JSSE or OpenSSL style configuration may be used regardless of
the SSLImplementation selected. JSSE style configuration is used below.
-->
<!--
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true">
<SSLHostConfig>
<Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
type="RSA" />
</SSLHostConfig>
</Connector>
-->
<!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2
This connector uses the APR/native implementation which always uses
OpenSSL for TLS.
Either JSSE or OpenSSL style configuration may be used. OpenSSL style
configuration is used below.
-->
<!--
<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>
-->
<!-- Define an AJP 1.3 Connector on port 8009 -->
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<!-- An Engine represents the entry point (within Catalina) that processes
every request. The Engine implementation for Tomcat stand alone
analyzes the HTTP headers included with the request, and passes them
on to the appropriate Host (virtual host).
Documentation at /docs/config/engine.html -->
<!-- You should set jvmRoute to support load-balancing via AJP ie :
<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
-->
<Engine name="Catalina" defaultHost="localhost">
<!--For clustering, please take a look at documentation at:
/docs/cluster-howto.html (simple how to)
/docs/config/cluster.html (reference documentation) -->
<!--
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
-->
<!-- Use the LockOutRealm to prevent attempts to guess user passwords
via a brute-force attack -->
<Realm className="org.apache.catalina.realm.LockOutRealm">
<!-- This Realm uses the UserDatabase configured in the global JNDI
resources under the key "UserDatabase". Any edits
that are performed against this UserDatabase are immediately
available for use by the Realm. -->
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<!-- SingleSignOn valve, share authentication between web applications
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
-->
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
主要标签结构如下:
Server 标签
Service 标签
Executor 标签
配置详解如下:
<!--
Connector 标签
Connector 标签⽤于创建链接器实例
默认情况下,server.xml 配置了两个链接器,⼀个⽀持HTTP协议,⼀个⽀持AJP协议
⼤多数情况下,我们并不需要新增链接器配置,只是根据需要对已有链接器进⾏优化
默认情况下,Service 并未添加共享线程池配置。 如果我们想添加⼀个线程池, 可以在
<Service> 下添加如下配置:
name:线程池名称,⽤于 Connector中指定
namePrefix:所创建的每个线程的名称前缀,⼀个单独的线程名称为
namePrefix+threadNumber
maxThreads:池中最⼤线程数
minSpareThreads:活跃线程数,也就是核⼼池线程数,这些线程不会被销毁,会⼀直存在
maxIdleTime:线程空闲时间,超过该时间后,空闲线程会被销毁,默认值为6000(1分钟),单位
毫秒
maxQueueSize:在被执⾏前最⼤线程排队数⽬,默认为Int的最⼤值,也就是⼴义的⽆限。除⾮特
殊情况,这个值 不需要更改,否则会有请求不会被处理的情况发⽣
prestartminSpareThreads:启动线程池时是否启动 minSpareThreads部分线程。默认值为
false,即不启动
threadPriority:线程池中线程优先级,默认值为5,值从1到10
className:线程池实现类,未指定情况下,默认实现类为
org.apache.catalina.core.StandardThreadExecutor。如果想使⽤⾃定义线程池⾸先需要实现
org.apache.catalina.Executor接⼝
-->
<Executor name="commonThreadPool"
namePrefix="thread-exec-"
maxThreads="200"
minSpareThreads="100"
maxIdleTime="60000"
maxQueueSize="Integer.MAX_VALUE"
prestartminSpareThreads="false"
threadPriority="5"
className="org.apache.catalina.core.StandardThreadExecutor"/>
Connector 标签
Connector 标签⽤于创建链接器实例
默认情况下,server.xml 配置了两个链接器,
⼀个⽀持HTTP协议
⼀个⽀持AJP协议
⼤多数情况下,我们并不需要新增链接器配置,只是根据需要对已有链接器进⾏优化
配置详解如下:
可以使⽤共享线程池
Engine 标签
Engine 表示 Servlet 引擎
一个service只有一个Engine标签
Engine下边有多个host标签
host 标签
<Host name="localhost" appBase="webapps2"
unpackWARs="true" autoDeploy="true">
<!-- SingleSignOn valve, share authentication between web applications
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
-->
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
unpackWARs 默认true,解压war包
autoDeploy 开启自动部署
appBase 项目部署的路径,默认在root目录下
name 虚拟主机地址
Valve 标签代表请求处理日志的配置
Context 标签
一个host 虚拟主机下可以包含多个 Context,具体配置如下:
<Host name="localhost" appBase="webapps" unpackWARs="true"
autoDeploy="true">
<!--
docBase:Web应⽤⽬录或者War包的部署路径。可以是绝对路径,也可以是相对于 Host appBase的
相对路径。
path:Web应⽤的Context 路径。如果我们Host名为localhost, 则该web应⽤访问的根路径为:
http://localhost:8080/web。
-->
<Context docBase="/Users/app/web_demo" path="/web"></Context>
<Valve className="org.apache.catalina.valves.AccessLogValve"
directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
Tomcat 核⼼流程源码剖析
Tomcat中的各容器组件都会涉及创建、销毁等,因此设计了⽣命周期接⼝Lifecycle进⾏统⼀规范,各容器组件实现该接⼝。
Lifecycle⽣命周期接⼝主要⽅法示意
Lifecycle⽣命周期接⼝继承体系示意
源码追踪部分我们关注两个流程:Tomcat启动流程和Tomcat请求处理流程
Tomcat启动流程
启动流程图
Tomcat 请求处理流程
请求处理流程分析
请求处理流程示意图
Mapper组件体系结构
Tomcat 类加载机制剖析
双亲委派机制的作⽤
1 防⽌重复加载同⼀个.class。通过委托去向上⾯问⼀问,加载过了,就不⽤再加载⼀遍。保证数据安全。
2 保证核⼼.class不能被篡改。通过委托⽅式,不会去篡改核⼼.class,即使篡改也不会去加载,即使加载也不会是同⼀个.class对象了。不同的加载器加载同⼀个.class也不是同⼀个.class对象。这样保证了class执⾏安全(如果⼦类加载器先加载,那么我们可以写⼀些与java.lang包中基础类同名的类, 然后再定义⼀个⼦类加载器,这样整个应⽤使⽤的基础类就都变成我们⾃⼰定义的类了。)
Object类 -----> ⾃定义类加载器(会出现问题的,那么真正的Object类就可能被篡改了)
Tomcat 的类加载机制
Tomcat 的类加载机制相对于 Jvm 的类加载机制做了⼀些改变。
没有严格的遵从双亲委派机制,也可以说打破了双亲委派机制
⽐如:有⼀个tomcat,webapps下部署了两个应⽤
app1/lib/a-1.0.jar com.test.edu.Abc
app2/lib/a-2.0.jar com.test.edu.Abc
不同版本中Abc类的内容是不同的,代码是不⼀样的
- 引导类加载器 和 扩展类加载器 的作⽤不变
- 系统类加载器正常情况下加载的是 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 性能优化策略
系统性能的衡量指标,主要是响应时间和吞吐量。
1) 响应时间:执⾏某个操作的耗时;
2) 吞吐量:系统在给定时间内能够⽀持的事务数量,单位TPS(Transactions PerSecond的缩写,也就是事务数/秒,⼀个事务是指⼀个客户机向服务器发送请求然后服务器做出反应的过程。
Tomcat优化从两个⽅⾯进⾏
1)JVM虚拟机优化(优化内存模型)
2)Tomcat⾃身配置的优化(⽐如是否使⽤了共享线程池?IO模型?)
没有说有明确的参数值⼤家直接去使⽤,必须根据⾃⼰的真实⽣产环境来进⾏调整,调优是⼀个过程
JVM虚拟机优化
Java 虚拟机的运⾏优化主要是内存分配和垃圾回收策略的优化
- 内存直接影响服务的运⾏效率和吞吐量
- 垃圾回收机制会不同程度地导致程序运⾏中断(垃圾回收策略不同,垃圾回收次数和回收效率都是不同的)
Java 虚拟机内存分配优化
虚拟机相关参数如下:
JVM内存模型回顾
参数调整示例
JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:MetaspaceSize=256m -
XX:MaxMetaspaceSize=512m"
将此参数配置到tomcat的bin目录的catalina.bat中(不是很了解bat,感觉可以删除上边一行的配置,不删除的话也是可以覆盖的)
重启tomcat之后,在win10环境中,用 netstat -ano| findstr 8080 命令查看Tomcat的 pid 为 660
再用JDK内存映射工具jmap -heap 660 命令 查看
可以看到参数都已生效
垃圾回收(GC)策略
垃圾回收性能指标
吞吐量:⼯作时间(排除GC时间)占总时间的百分⽐, ⼯作时间并不仅是程序运⾏的时间,还包含内存分配时间。
暂停时间:由垃圾回收导致的应⽤程序停⽌响应次数/时间。
垃圾回收器分类:
- 串⾏收集器(Serial Collector)
单线程执⾏所有的垃圾回收⼯作, 适⽤于单核CPU服务器
⼯作进程-----|(单线程)垃圾回收线程进⾏垃圾收集|—⼯作进程继续 - 并⾏收集器(Parallel Collector)
⼯作进程-----|(多线程)垃圾回收线程进⾏垃圾收集|—⼯作进程继续
⼜称为吞吐量收集器(关注吞吐量), 以并⾏的⽅式执⾏年轻代的垃圾回收, 该⽅式可以显著降低垃圾回收的开销(指多条垃圾收集线程并⾏⼯作,但此时⽤户线程仍然处于等待状态)。适⽤于多处理器或多线程硬件上运⾏的数据量较⼤的应⽤ - 并发收集器(Concurrent Collector)
以并发的⽅式执⾏⼤部分垃圾回收⼯作,以缩短垃圾回收的暂停时间。适⽤于那些响应时间优先于吞吐量的应⽤, 因为该收集器虽然最⼩化了暂停时间(指⽤户线程与垃圾收集线程同时执⾏,但不⼀定是并⾏的,可能会交替进⾏), 但是会降低应⽤程序的性能 - CMS收集器(Concurrent Mark Sweep Collector)
并发标记清除收集器, 适⽤于那些更愿意缩短垃圾回收暂停时间并且负担的起与垃圾回收共享处理器资源的应⽤ - G1收集器(Garbage-First Garbage Collector)
适⽤于⼤容量内存的多核服务器, 可以在满⾜垃圾回收暂停时间⽬标的同时, 以最⼤可能性实现⾼吞吐量(JDK1.7之后)
查看当前进程的使用的垃圾收集器:
要用到JDK的bin目录下的jconsole.exe,双击打开,可以看到
选择Tomcat对应的pid 660,并且以不安全连接进入,选择VM概要,如下
垃圾回收器参数
垃圾回收器如何调优
在bin/catalina.sh的脚本中 , 追加如下配置 :
JAVA_OPTS="-XX:+UseConcMarkSweepGC"
Tomcat 配置调优
调整tomcat线程池
调整tomcat的连接器
调整tomcat/conf/server.xml 中关于链接器的配置可以提升应⽤服务器的性能。
禁⽤ A JP 连接器
调整 IO 模式
Tomcat8之前的版本默认使⽤BIO(阻塞式IO),对于每⼀个请求都要创建⼀个线程来处理,不适合⾼并发;Tomcat8以后的版本默认使⽤NIO模式(⾮阻塞式IO)
当Tomcat并发性能有较⾼要求或者出现瓶颈时,我们可以尝试使⽤APR模式,APR(Apache PortableRuntime)是从操作系统级别解决异步IO问题,使⽤时需要在操作系统上安装APR和Native(因为APR原理是使⽤使⽤JNI技术调⽤操作系统底层的IO接⼝)
动静分离
可以使⽤Nginx+Tomcat相结合的部署⽅案,Nginx负责静态资源访问,Tomcat负责Jsp等动态资源访问处理(因为Tomcat不擅⻓处理静态资源)