百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Tomcat原理剖析及性能调优

nanshan 2024-11-24 19:36 12 浏览 0 评论

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.3Coyote内部组件功能

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.5Tomcat配置文件详解

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性能调优

我们看一个服务器的性能指标主要是从两个方面着手:

  1. 响应时间,tomcat完成一个操作需要的时间
  2. 吞吐量,单位时间内能够完成的操作的数量,单位为数量/秒,一个操作从发起到服务器响应请求。

我们tomcat性能优化从两个方面入手:

  1. 优化JVM内存模型
  2. Tomcat调整线程池或者IO模型

3.1JVM内存模型及JVM内存参数调优

理解了JVM内存模型,我们才能更好的去对JVM内存调优。

本地方法栈:C++Native执行所需的栈区,为线程私有

程序计数器:保存程序执行的位置,为线程私有

栈:程序运行时方法的临时变量保存区域,为线程私有,保存的也是线程的执行位置

堆:存储对象

元数据区(方法区):静态变量、方法、类加载器保存区域

1)JVM内存参数调优

我们对JVM内存优化主要是对堆内存进行调整,其中JVM内存参数如下表:

参数

说明

配置建议

-server

启动Server,以服务端模式运?

服务端模式建议开启

-Xms

最?堆内存

建议与-Xmx设置相

-Xmx

最?堆内存

建议设置为可?内存的80%,这个可用内存是其他软件使用完毕后还可以剩下使用的内存

-XX:MetaspaceSize

元空间初始值


-
XX:MaxMetaspaceSize

元空间最?内存

默认?限

-XX:NewRatio

年轻代和?年代???值,取值为整数,默
认为2

不需要修改

-XX:SurvivorRatio

Eden区与Survivor区??的?值,取值为整
数,默认为8

不需要修改

参数调整示例:

我的计算机是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内存使用情况


  1. 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垃圾收集器。 当并?收集器?法满?应?的延迟需
求是,推荐使?CMS或G1收集器。启?该选项后, -XX:+UseParNewGC?动启?。

-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处理。

相关推荐

实战派 | Java项目中玩转Redis6.0客户端缓存

铺垫首先介绍一下今天要使用到的工具Lettuce,它是一个可伸缩线程安全的redis客户端。多个线程可以共享同一个RedisConnection,利用nio框架Netty来高效地管理多个连接。放眼望向...

轻松掌握redis缓存穿透、击穿、雪崩问题解决方案(20230529版)

1、缓存穿透所谓缓存穿透就是非法传输了一个在数据库中不存在的条件,导致查询redis和数据库中都没有,并且有大量的请求进来,就会导致对数据库产生压力,解决这一问题的方法如下:1、使用空缓存解决对查询到...

Redis与本地缓存联手:多级缓存架构的奥秘

多级缓存(如Redis+本地缓存)是一种在系统架构中广泛应用的提高系统性能和响应速度的技术手段,它综合利用了不同类型缓存的优势,以下为你详细介绍:基本概念本地缓存:指的是在应用程序所在的服务器内...

腾讯云国际站:腾讯云服务器如何配置Redis缓存?

本文由【云老大】TG@yunlaoda360撰写一、安装Redis使用包管理器安装(推荐)在CentOS系统中,可以通过yum包管理器安装Redis:sudoyumupdate-...

Spring Boot3 整合 Redis 实现数据缓存,你做对了吗?

你是否在开发互联网大厂后端项目时,遇到过系统响应速度慢的问题?当高并发请求涌入,数据库压力剧增,响应时间拉长,用户体验直线下降。相信不少后端开发同行都被这个问题困扰过。其实,通过在SpringBo...

【Redis】Redis应用问题-缓存穿透缓存击穿、缓存雪崩及解决方案

在我们使用redis时,也会存在一些问题,导致请求直接打到数据库上,导致数据库挂掉。下面我们来说说这些问题及解决方案。1、缓存穿透1.1场景一个请求进来后,先去redis进行查找,redis存在,则...

Spring boot 整合Redis缓存你了解多少

在前一篇里面讲到了Redis缓存击穿、缓存穿透、缓存雪崩这三者区别,接下来我们讲解Springboot整合Redis中的一些知识点:之前遇到过,有的了四五年,甚至更长时间的后端Java开发,并且...

揭秘!Redis 缓存与数据库一致性问题的终极解决方案

在现代软件开发中,Redis作为一款高性能的缓存数据库,被广泛应用于提升系统的响应速度和吞吐量。然而,缓存与数据库之间的数据一致性问题,一直是开发者们面临的一大挑战。本文将深入探讨Redis缓存...

高并发下Spring Cache缓存穿透?我用Caffeine+Redis破局

一、什么是缓存穿透?缓存穿透是指查询一个根本不存在的数据,导致请求直接穿透缓存层到达数据库,可能压垮数据库的现象。在高并发场景下,这尤其危险。典型场景:恶意攻击:故意查询不存在的ID(如负数或超大数值...

Redis缓存三剑客:穿透、雪崩、击穿—手把手教你解决

缓存穿透菜小弟:我先问问什么是缓存穿透?我听说是缓存查不到,直接去查数据库了。表哥:没错。缓存穿透是指查询一个缓存中不存在且数据库中也不存在的数据,导致每次请求都直接访问数据库的行为。这种行为会让缓存...

Redis中缓存穿透问题与解决方法

缓存穿透问题概述在Redis作为缓存使用时,缓存穿透是常见问题。正常查询流程是先从Redis缓存获取数据,若有则直接使用;若没有则去数据库查询,查到后存入缓存。但当请求的数据在缓存和数据库中都...

Redis客户端缓存的几种实现方式

前言:Redis作为当今最流行的内存数据库和缓存系统,被广泛应用于各类应用场景。然而,即使Redis本身性能卓越,在高并发场景下,应用于Redis服务器之间的网络通信仍可能成为性能瓶颈。所以客户端缓存...

Nginx合集-常用功能指导

1)启动、重启以及停止nginx进入sbin目录之后,输入以下命令#启动nginx./nginx#指定配置文件启动nginx./nginx-c/usr/local/nginx/conf/n...

腾讯云国际站:腾讯云怎么提升服务器速度?

本文由【云老大】TG@yunlaoda360撰写升级服务器规格选择更高性能的CPU、内存和带宽,以提供更好的处理能力和网络性能。优化网络配置调整网络接口卡(NIC)驱动,优化TCP/IP参数...

雷霆一击服务器管理员教程

本文转载莱卡云游戏服务器雷霆一击管理员教程(搜索莱卡云面版可搜到)首先你需要给服务器设置管理员密码,默认是空的管理员密码在启动页面进行设置设置完成后你需要重启服务器才可生效加入游戏后,点击键盘左上角E...

取消回复欢迎 发表评论: