1 Tomcat的原理和配置文件详解
1.1 tomcat服务器总体架构
tomcat服务器具备两种能力,一种是作为一个Http服务器,另外一个则是作为Servlet容器,两大功能将tomcat拆分为两个组件,Coyote(连接器)与Catalina(容器)两个组件。其中Coyote负责处理Socket通讯,将Socket解析封装为Request对象(非Java ee的Request对象),Catalina负责Servlet初始化、加载等。
1.2 Tomcat连接器组件Coyote
在进行Coyote组件的分析之前,我们先来看下Coyote组件的职能:
1)处理socket请求
2)与Catalina组件解耦
3)将请求封装成request对象,并将该对象转发给Catalina组件封装成HttpServletRequst对象
4)负责网络层和传输层的内容
由上面我们可以知道,Tomcat连接器组件主要是处理TCP/IP模型下的网络层和传输层的内容,具体的TCP/IP模型可参考如下图。
Tomcat主要是职责是处理TCP/IP模型中的传输层和应用层,其中应用层支持的协议有HTTP(1.1与2.0版本,默认为1.1版本),传输层支持NIO和ARP两种传输模型,这里具体的I/O传输模型这里不多做分析。
1.3 Coyote内部组件功能
Coyote组件 | 功能 |
Adapter | 位于org.apache.coyote包中,Tomcat中的Catalina组件入口,将tomcat的request和response 转换成ServletRequest和HttpResponse,并用Catalina容器 |
Processor | 所有协议处理器的通用接口(应用层协议),加工处理Socket连接并封装成tomcat Request和Response对象,并通过Adapter传入容器中 |
ProtocolHandler | Tomcat协议接口,定义具体协议的处理能力。 |
EndPoint | Coyote通讯端点,监听Socket请求,向Processor提供字节流 |
发起请求------》EndPoint------提供字节流----->Processor---提供res和rep对象----》Adapter --提供ServletRequest、ServletResponse--》Catalina容器。
1.4 Tomcat Catalina组件
Tomcat的核心组件,可以认为所有的组件都是为Catalina组件服务,Catalina是一个Servlet容器。Tomcat启动都会示例化一个Catalina实例,Catalina实例会读取conf/server.xml这个配置文件完成其他组件的实例创建。查看Tomcat的conf/server.xml文件,Catalina 可以实例化一个Server容器和多个Service容器。Service下面可以配置多个Connector组件绑定到一个Container容器。
我们读配置文件,可看到Catalina容器组件可由具体的以下几种容器组件构成。
Engine: Catalina的引擎,在Service下只能有一个Engine
Host:一个站点,位于Engine下面,可以配置多个虚拟站点地址,可以包含多个Content
Content:一个站点程序,位于Host下面,可以包含多个Wrapper
Wrapper:一个Wrapper代表一个Servlet实例,容器最底层,不在包含其他容器
1.5 Tomcat配置文件详解
Tomcat核心配置文件位于conf/server.xml。
<?xml version="1.0" encoding="UTF-8"?>
<!--
根节点
port:为关闭服务的监听端口
shutdown:关闭服务的指令
-->
<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加载以及管理 -->
<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-->
<!--
service共享线程池
name: 线程池名称,在Connector可指定使用的共享线程池名称
namePrefix:创建的线程池名称前缀,线程名称为namePrefix+threadNumber
maxThreads:最大线程池数量
minSpareThreads:活跃线程数量,线程不会被销毁
maxIdlTime:线程空闲时间,超过该时间销毁空线程,默认6000
maxQueueSize: 线程池最大排队数量,默认值为int最大值。超过这个值tomcat不在处理请求
prestartminSpareThreads:启动线程池时是否启动 minSpareThreads部分线程。默认值为false,即不启动
threadPriority:线程池中线程优先级,默认值为5,值从1到10
className:线程池实现类,未指定情况下,默认实现类为org.apache.catalina.core.StandardThreadExecutor。如果想使⽤⾃定义线程池⾸先需要实现org.apache.catalina.Executor接⼝
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->
<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"/>
<!--
port:端⼝号,可设置为0,Tomcat随机拿一个可以使用的端口给Connector使用。
protocol: 访问协议。 默认为 HTTP/1.1 , 并采⽤⾃动切换机制选择⼀个基于 JAVA
NIO 的链接器或者基于本地APR的链接器(根据本地是否含有Tomcat的本地库判定)
connectionTimeOut: 请求超时时间, 单位为 毫秒。 -1 表示不超时。
redirectPort:HTTPS默认端口
executor:制定使用线程池
Engine 标签
Engine 表示 Servlet 引擎
Host 标签
Host 标签⽤于配置⼀个虚拟主机
URIEncoding:
⽤于指定编码URI的字符编码, Tomcat8.x版本默认的编码为 UTF-8 , Tomcat7.x版本默认为ISO-
8859-1
-->
<!--org.apache.coyote.http11.Http11NioProtocol , ⾮阻塞式 Java NIO 链接器-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!-- 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">
<Context docBase="/webapps/demo" path="/demo"></Context>
<!-- 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>
2 Tomcat如何处理一个请求(源码追踪)
在进行源码追踪先,我们先下载tomcat-8.5.72-src源码和tomcat-8.5.72,下载地址:Apache Tomcat® - Apache Tomcat 8 Software Downloads
使用IDEA打开tomcat源码工程
因为tomcat 不是用maven构建的,将tomcat源码工程新建pom.xml文件转为maven项目,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>1.8</source>
<target>1.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>
如果你下载的是tomcat10+版本,还需要在pom中添加下面这两个jar包:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>jakartaee-migration</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>biz.aQute.bnd</groupId>
<artifactId>biz.aQute.bndlib</artifactId>
<version>5.3.0</version>
<scope>provided</scope>
</dependency>
在pom.xml右键,选择Add as Maven project
在org.apache.catalina.startup.ContextConfig#contextConfig 方法中添加jsp解析,
context.addServletContainerInitializer(new JasperInitializer(), null);
因为tomcat源码工程的webapps里面的工程都是没有经过编译的示例工程,将下载的经过编译的二进制工程中的webapps和conf目录覆盖到tomcat源代码工程中。并在jvm启动参数中添加tomcat启动参数,指定tomcat主目录和配制文件目录。
-Dcatalina.home=D:\projects\apache-tomcat-8.5.50-src\
-Dcatalina.base=D:\projects\apache-tomcat-8.5.50-src\
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=D:\projects\apache-tomcat-8.5.50-src/conf/logging.properties
Tomcat启动入口在org.apache.catalina.startup.Bootstrap中的main函数中,启动它。启动成功后访问127.0.0.1:8080 ,成功!
Tomcat请求分析:
1、org.apache.tomcat.util.net.NioEndpoint.SocketProcessor#doRun --> 接收Socket请求
org.apache.coyote.http11.Http11Processor#service--> 封装tomcat req 和res
2、org.apache.catalina.connector.CoyoteAdapter#service --> 将tomcat req 和res 转换为ServletRequest 和ServletResponse
3、org.apache.catalina.core.StandardEngineValve#invoke -->根据请求的服务器名称,选择合适的子主机来处理此请求。 如果找不到匹配的主机,则返回相应的 HTTP 错误。
4、org.apache.catalina.core.StandardHostValve#invoke -->根据Content部署的地址查找应用
5、org.apache.catalina.core.StandardContextValve#invoke -->根据指定的请求 URI,选择适当的子 Wrapper 来处理此请求。 如果找不到匹配的 Wrapper,则返回相应的 HTTP 错误。在进行Wrapper 查找之前先判断路径是有WEB-INF等路径,这些路径返回404
6、org.apache.catalina.core.StandardWrapperValve#invoke -->调用正在管理的 servlet,遵守有关 servlet 生命周期和 SingleThreadModel 支持的规则。
7、org.apache.catalina.core.ApplicationFilterChain#doFilter -->调用此链中的下一个过滤器,传递指定的请求和响应。 如果此链中没有更多过滤器,则调用 servlet 本身的service()方法。
3 Tomcat性能调优
我们看一个服务器的性能指标主要是从两个方面着手:
- 响应时间,tomcat完成一个操作需要的时间
- 吞吐量,单位时间内能够完成的操作的数量,单位为数量/秒,一个操作从发起到服务器响应请求。
我们tomcat性能优化从两个方面入手:
- 优化JVM内存模型
- Tomcat调整线程池或者IO模型
3.1 JVM内存模型及JVM内存参数调优
理解了JVM内存模型,我们才能更好的去对JVM内存调优。
本地方法栈:C++Native执行所需的栈区,为线程私有
程序计数器:保存程序执行的位置,为线程私有
栈:程序运行时方法的临时变量保存区域,为线程私有,保存的也是线程的执行位置
堆:存储对象
元数据区(方法区):静态变量、方法、类加载器保存区域
1)JVM内存参数调优
我们对JVM内存优化主要是对堆内存进行调整,其中JVM内存参数如下表:
参数 | 说明 | 配置建议 |
-server | 启动Server,以服务端模式运⾏ | 服务端模式建议开启 |
-Xms | 最⼩堆内存 | 建议与-Xmx设置相 |
-Xmx | 最⼤堆内存 | 建议设置为可⽤内存的80%,这个可用内存是其他软件使用完毕后还可以剩下使用的内存 |
-XX:MetaspaceSize | 元空间初始值 | |
- | 元空间最⼤内存 | 默认⽆限 |
-XX:NewRatio | 年轻代和⽼年代⼤⼩⽐值,取值为整数,默 | 不需要修改 |
-XX:SurvivorRatio | Eden区与Survivor区⼤⼩的⽐值,取值为整 | 不需要修改 |
参数调整示例:
我的计算机是8G内存,开机后剩余内存5G,我以服务模仿是运行,最大堆内存和最小堆内存相同,占用5G * 80% = 4G,其他参数不需要调整。
JAVA_OPTS="-server -Xms4096m -Xmx4096m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
使用jmap 查看tomcat内存使用情况:
netstat -ano 找到tomcat的pid
输入jmap -heap 8940 ,查看tomcat内存使用情况
- JVM垃圾回收机制调参
GC策略 | 说明 |
串行收集器 | 单线程执行所有的垃圾回收工作,适用于单核CPU服务器。工作进程--->同一个线程GC工作-->工作进程 |
并行收集器 | 以并行的方式执行年轻代的垃圾回收,可以降低垃圾回收的开销。适用于多核处理器多线程的硬件上的数据量较大的应用。工作进程--->多个线程GC工作--->工作进程 |
并发收集器 | 并发的方式执行大部分垃圾回收工作,以缩短回收暂停时间。使用于那些响应时间优先于吞吐量的应用,因为该收集器缩短了暂停时间,但会降低应用程序性能。 |
CMS收集器 | 并发标记清除收集器,使用于那些更愿意缩短垃圾回收暂停时间并负担起与垃圾回收共享处理资源的应用。 |
G1收集器 | 适用于大容量内存的多核服务器,可以在满足垃圾回收暂停时间目标的同时,以最大可能性实现高吞吐量。该机制在JDK1.7之后才支持。 |
垃圾回收机制参数配置如下表所示:
参数 | 描述 |
-XX:+UseSerialGC | 启⽤串⾏收集器 |
-XX:+UseParallelGC | 启⽤并⾏垃圾收集器,配置了该选项,那么 -XX:+UseParallelOldGC默认启⽤ |
-XX:+UseParNewGC | 年轻代采⽤并⾏收集器,如果设置了 -XX:+UseConcMarkSweepGC选项,⾃动启⽤ |
-XX:ParallelGCThreads | 年轻代及⽼年代垃圾回收使⽤的线程数。默认值依赖于JVM使⽤的CPU个数 |
-XX:+UseConcMarkSweepGC(CMS) | 对于⽼年代,启⽤CMS垃圾收集器。 当并⾏收集器⽆法满⾜应⽤的延迟需 |
-XX:+UseG1GC | 启⽤G1收集器。 G1是服务器类型的收集器, ⽤于多核、⼤内存的机器。它在保持⾼吞吐量的情况下,⾼概率满⾜GC暂停时间的⽬标。 |
参数示例:
JAVA_OPTS="-XX:+UseConcMarkSweepGC"
3.2 tomcat配置调优
- 在配置文件中,调整conf/server.xml,配置tomcat线程池
对性能影响的线程池参数如下表所示:
参数 | 说明 |
maxConnections | 最⼤连接数,当到达该值后,服务器接收但不会处理更多的请求, 额外的请求将会阻塞直到连接数低于maxConnections 。可通过ulimit -a 查看服务器限制。对于CPU要求更⾼(计算密集型)时,建议不要配置过⼤ ; 对于CPU要求不是特别⾼时,建议配置在2000左右(受服务器性能影响)。 当然这个需要服务器硬件的⽀持。 |
maxThreads | 最⼤线程数,需要根据服务器的硬件情况,进⾏⼀个合理的设置 |
acceptCount | 最⼤排队等待数,当服务器接收的请求数量到达maxConnections ,此时Tomcat会将后⾯的请求,存放在任务队列中进⾏排序, acceptCount指的就是任务队列中排队等待的请求数 。⼀台Tomcat的最⼤的请求处理数量,是maxConnections+acceptCount。 |
- 配置conf/server.xml,禁用AJP连接器
- 配置conf/server.xml,调整IO模式
Tomcat8之前默认使用BIO,每一个请求创建一个线程,不适用高并发;Tomcat8以后版本默认使用NIO。
Tomcat并发性能有较⾼要求或者出现瓶颈时,我们可以尝试使⽤APR模式, APR(Apache PortableRuntime)是从操作系统级别解决异步IO问题,使⽤时需要在操作系统上安装APR和Native(因为APR原理是使⽤使⽤JNI技术调⽤操作系统底层的IO接⼝)
- 动静分离
可以使用Nginx+tomcat相结合,tomcat比较擅长处理动态资源,可以将静态资源丢给Nginx处理。