栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Tomcat笔记

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Tomcat笔记

Tomcat简介及优化思路 Tomcat 处理请求大致流程和架构 请求流程

  • 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服务器会把响应发送给客户端
Tomact整体架构

tomcat有两个主要功能:

  • 和客户端浏览器进⾏交互,进⾏socket通信,将字节流和Request/Response等对象进⾏转换
  • 处理业务逻辑

其中负责客户端浏览器进⾏交互的叫连接器(connector)

负责处理业务逻辑的叫容器(container)

Coyote 简介

Coyote 是Tomcat 中连接器的组件名称 , 是对外的接⼝。客户端通过Coyote与服务器建⽴连接、发送请求并接受响应 。 其主要功能如下:

  • Coyote 封装了底层的⽹络通信(Socket 请求及响应处理)
  • Coyote 使Catalina 容器(容器组件)与具体的请求协议及IO操作⽅式完全解耦
  • Coyote 将Socket 输⼊转换封装为 Request 对象,进⼀步封装后交由Catalina 容器进⾏处理,处理请求完成后, Catalina 通过Coyote 提供的Response 对象将结果写⼊输出流
  • Coyote 负责的是具体协议(应⽤层)和IO(传输层)相关内容

Tomcat Coyote ⽀持的协议与 IO模型如下:

协议:

HTTP/1.1默认协议,大部分web采用的协议
AJP用于和WX集成(比如Apache),实现了对静态资源的优化和集群部署,当前支持 AJP/1.3
HTTP/2HTTP 2.0大大提高了web的性能,从8.5和9.0后版本后开始支持

IO:

NIO默认IO,非阻塞IO,采用Java NIO类库实现
NIO2异步IO,采用jdk7的NIO2类库实现
APR采用Apche可移植库实现,是C/C++编写的本地库,使用时需要单独安装APR库

在 8.0 之前 ,Tomcat 默认采⽤的I/O⽅式为 BIO,之后改为 NIO。 ⽆论 NIO、NIO2 还是 APR, 在性能⽅⾯均优于以往的BIO。 如果采⽤APR, 甚⾄可以达到 Apache HTTP Server 的影响性能。

内部组件及流程

组件作⽤描述
EndPointEndPoint 是 Coyote 通信端点,即通信监听的接⼝,是具体Socket接收和发送处理器,是对传输层的抽象,因此EndPoint⽤来实现TCP/IP协议的
ProcessorProcessor 是Coyote 协议处理接⼝,如果说EndPoint是⽤来实现TCP/IP协议的,那么Processor⽤来实现HTTP协议,Processor接收来⾃EndPoint的Socket,读取字节流解析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理,Processor是对应⽤层协议的抽象
ProtocolHandlerCoyote 协议接⼝, 通过Endpoint 和 Processor , 实现针对具体协议的处理能⼒。Tomcat 按照协议和I/O 提供了6个实现类 : AjpNioProtocol ,AjpAprProtocol, AjpNio2Protocol , Http11NioProtocol ,Http11Nio2Protocol ,Http11AprProtocol
Adapter由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat定义了⾃⼰的Request类来封装这些请求信息。ProtocolHandler接⼝负责解析请求并⽣成Tomcat Request类。但是这个Request对象不是标准的ServletRequest,不能⽤Tomcat Request作为参数来调⽤容器。Tomcat设计者的解决⽅案是引⼊CoyoteAdapter,这是适配器模式的经典运⽤,连接器调⽤CoyoteAdapter的Sevice⽅法,传⼊的是Tomcat Request对象,CoyoteAdapter负责将Tomcat Request转成ServletRequest,再调⽤容器
Catalina 简介

Tomcat是⼀个由⼀系列可配置(conf/server.xml)的组件构成的Web容器,⽽Catalina是Tomcat的servlet容器。从另⼀个⻆度来说,Tomcat 本质上就是⼀款 Servlet 容器, 因为 Catalina 才是 Tomcat 的核⼼ , 其他模块都是为Catalina 提供⽀撑的。 ⽐如 : 通过 Coyote 模块提供链接通信,Jasper 模块提供 JSP 引擎,Naming 提供JNDI 服务,Juli 提供⽇志服务。

可以认为Tomcat就是⼀个Catalina的实例,因为Catalina是Tomcat的核⼼,Tomcat 启动的时候会初始化Catalina实例,Catalina实例通过加载server.xml完成其他实例的创建,创建并管理⼀个Server,Server创建并管理多个服务,每个服务⼜可以有多个Connector和⼀个Container。

tomact各个部分的分工如下:

  • Catalina:负责解析Tomcat的配置⽂件(server.xml) , 以此来创建服务器Server组件并进⾏管理

  • Server:表示整个Catalina Servlet容器以及其它组件,负责组装并启动Servlet引擎,Tomcat连接器。Server通过实现Lifecycle接⼝,提供了⼀种优雅的启动和关闭整个系统的⽅式

  • Service:服务是Server内部的组件,⼀个Server包含多个Service。它将若⼲个Connector组件绑定到⼀个Container

  • Container:容器,负责处理⽤户的servlet请求,并返回对象给⽤户的模块

Container结构

Container组件下有⼏种具体的组件,分别是Engine、Host、Context和Wrapper。这4种组件(容器)是⽗⼦关系。Tomcat通过⼀种分层的架构,使得Servlet容器具有很好的灵活性。

  • Engine:表示整个Catalina的Servlet引擎,⽤来管理多个虚拟站点,⼀个Service最多只能有⼀个Engine,但是⼀个引擎可包含多个Host

  • Host:代表⼀个虚拟主机,或者说⼀个站点,可以给Tomcat配置多个虚拟主机地址,⽽⼀个虚拟主机下可包含多个Context

  • Context:表示⼀个Web应⽤程序, ⼀个Web应⽤可包含多个Wrapper

  • Wrapper:表示⼀个Servlet,Wrapper 作为容器中的最底层,不能包含⼦容器

上述组件的配置其实就体现在conf/server.xml中。

Tomcat配置

tomcat的配置文件是conf/server.xml

主要标签结构



	
	
	
	
	

Server



































​	

​	







 ...




Service



...


Executor


Connector

Connector 标签⽤于创建链接器实例

默认情况下,server.xml 配置了两个链接器,⼀个⽀持HTTP协议,⼀个⽀持AJP协议

⼤多数情况下,我们并不需要新增链接器配置,只是根据需要对已有链接器进⾏优化





可以使⽤共享线程池:


Engine

Engine 表示 Servlet 引擎




...


Host

Host 标签⽤于配置⼀个虚拟主机



 ...


Context

Context 标签⽤于配置⼀个Web应⽤


        
        
        
         
        
      
Tomcat 启动流程

Tomcat 处理请求流程

当一个请求到达tomcat时,tomcat的mapper组件会把url和host、context、wrapper进行映射,比如http://localhost:8080/web_demo/resume/addResume,

host是负责映射localhost、context负责web_demo、wrapper负责resume/addResume 这样就可以找到对应的Servlet出来业务

请求处理流程如下:

Mapper组件体系结构:

JVM类加载机制和Tomcat类加载机制 JVM类加载机制

JVM 的类加载加载流程是由类加载器完成的。
java中主要有以下几种类加载器:

  • 引导类加载器 BootstrapClassLoader: 也叫启动类加载器,负责加载JRE的lib目录下的核心类库,例如rt.jar等

  • 扩展类加载器 ExtClassLoader:负责加载扩展库JAVA_HOME/lib/ext目录下的jar中的类,如classpath中的jre,javax.*或者java.ext.dir指定位置中的类

  • 系统类加载器 AppClassLoader:负责加载ClassPath路径下的类包,主要是加载自己定义的那些类

  • 自定义加载器 :负责加载自定义路径下的类

当 JVM 运⾏过程中,⽤户⾃定义了类加载器去加载某些类时,会按照下⾯的步骤(⽗类委托机制)

1.⽤户⾃⼰的类加载器,把加载请求传给⽗加载器,⽗加载器再传给其⽗加载器,⼀直到加载器树的顶层

2.最顶层的类加载器⾸先针对其特定的位置加载,如果加载不到就转交给⼦类

3.如果⼀直到底层的类加载都没有加载到,那么就会抛出异常 ClassNotFoundException

因此,按照这个过程可以想到,如果同样在 classpath 指定的⽬录中和⾃⼰⼯作⽬录中存放相同的class,会优先加载 classpath ⽬录中的⽂件

Jvm类加载器的父子(委托)关系 什么是双亲委派机制

当某个类被加载时,当前的类加载器会先把这个任务委托给父类加载器,递归这个操作,只有父类加载器找不到这类的时候,才会自己去加载这个类。
例如加载Test对象 AppClassLoader会先把这个任务交给ExtClassLoader,ExtClassLoader又会上交给BootstrapClassLoader,因为BootstrapClassLoader负责加载核心类库的类,ExtClassLoader负责加载扩展类,因此在它们负责的路径下是找不到这个类的,最后返回给AppClassLoader完成加载。

双亲委派机制的作用
  • 防止类被重复加载:向上委托的时候会判断该类是否已经被加载过了,如果父类加载器已经加载了,就不用再重复加载了.

  • 防止核心Api被随意篡改:通过委托⽅式,不会去篡改核⼼.class,即使篡改也不会去加载,即使加载也不会是同⼀个.class对象了。不同的加载器加载同⼀个.class也不是同⼀个.class对象。这样保证了class执⾏安全(如果⼦类加载器先加载,那么我们可以写⼀些与java.lang包中基础类同名的类, 然后再定义⼀个⼦类加载器,这样整个应⽤使⽤的基础类就都变成我们⾃⼰定义的类了。)

Jvm类加载器源码

类加载器的loadClass方法:java.lang.ClassLoader#loadClass(java.lang.String, boolean)

protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
  • getClassLoadingLock;对目标类进行加锁,防止多个线程同时加载同一个类,造成重复加载。
  • findLoadedClass(name); 判断目标类是否已经被加载,还未被加载则返回null。
  • 双亲委派关键逻辑:
if (parent != null) {
       c = parent.loadClass(name, false);
} else {
      c = findBootstrapClassOrNull(name);
}

可以看到在这里获取当前类加载器的父类加载器,如果不等于null则由父类加载器加载目标类,并且父类加载器也同样实现了该逻辑。
如果为null则调用findBootstrapClassOrNull方法。
那么什么时候或者什么样的类加载器,父类加载器才会为空呢?

扩展类加载器的上级加载器为null,这是因为启动类加载器不是由java编写的,所以在jvm中为null.
回到上面的源码,也就是当目标类委托到扩展类加载器之后继续向上委托时就会执行findBootstrapClassOrNull方法。

  • c = findClass(name);当目标类c为null是,则会调用findClass查找该类再自己的加载路径范围之类。如果没有找到,则返回给下级类加载器。

简单总结就是先找父亲加载,不行就给儿子加载。

Tomcat的类加载机制

Tomcat 的类加载机制相对于 Jvm 的类加载机制做了⼀些改变。没有严格的遵从双亲委派机制,也可以说打破了双亲委派机制,这主要是因为tomcat是一个web服务器需要支持部署多个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。

Tomcat类加载器

  • 引导类加载器 BootstrapClassLoader和扩展类加载器 ExtClassLoader的作⽤不变

  • 系统类加载器 AppClassLoader正常情况下加载的是 CLASSPATH 下的类,但是 Tomcat 的启动脚本并未使⽤该变量,⽽是加载tomcat启动的类,⽐如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下

  • commonClassLoader:通用类加载器加载Tomcat使用以及应用通用的一些类,位于CATALINA_HOME/lib下, 比如servlet-api.jar

  • catalinaClassLoader:tomcat容器私有的类加载器,加载服务器内部可⻅类对于Webapp不可见

  • sharedClassLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见

  • WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;每个 webappClassLoader加载自己的目录下的class文件(本应⽤程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类),不会传递给父类加载器,打破了双 亲委派机制。

tomcat 8.5 默认改变了严格的双亲委派机制

  • ⾸先从 Bootstrap Classloader加载指定的类(用来加载基础类, 比如:Object,String等)
  • 如果未加载到,则从 /WEB-INF/classes加载
  • 如果未加载到,则从 /WEB-INF/lib clazz = findLoadedClass0(name); if (clazz != null) { if (log.isDebugEnabled()) { log.debug(" Returning class from cache"); } if (resolve) { resolveClass(clazz); } return clazz; } // (0.1) Check our previously loaded class cache clazz = findLoadedClass(name); if (clazz != null) { if (log.isDebugEnabled()) { log.debug(" Returning class from cache"); } if (resolve) { resolveClass(clazz); } return clazz; } // (0.2) Try loading the class with the system class loader, to prevent // the webapp from overriding Java SE classes. This implements // SRV.10.7.2 String resourceName = binaryNameToPath(name, false); ClassLoader javaseLoader = getJavaseClassLoader(); boolean tryLoadingFromJavaseLoader; try { // Use getResource as it won't trigger an expensive // ClassNotFoundException if the resource is not available from // the Java SE class loader. However (see // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for // details) when running under a security manager in rare cases // this call may trigger a ClassCircularityError. // See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for // details of how this may trigger a StackOverflowError // Given these reported errors, catch Throwable to ensure any // other edge cases are also caught URL url; if (securityManager != null) { PrivilegedAction dp = new PrivilegedJavaseGetResource(resourceName); url = AccessController.doPrivileged(dp); } else { url = javaseLoader.getResource(resourceName); } tryLoadingFromJavaseLoader = (url != null); } catch (Throwable t) { // Swallow all exceptions apart from those that must be re-thrown ExceptionUtils.handleThrowable(t); // The getResource() trick won't work for this class. We have to // try loading it directly and accept that we might get a // ClassNotFoundException. tryLoadingFromJavaseLoader = true; } if (tryLoadingFromJavaseLoader) { try { clazz = javaseLoader.loadClass(name); if (clazz != null) { if (resolve) { resolveClass(clazz); } return clazz; } } catch (ClassNotFoundException e) { // Ignore } } // (0.5) Permission to access this class when using a SecurityManager if (securityManager != null) { int i = name.lastIndexOf('.'); if (i >= 0) { try { securityManager.checkPackageAccess(name.substring(0,i)); } catch (SecurityException se) { String error = sm.getString("webappClassLoader.restrictedPackage", name); log.info(error, se); throw new ClassNotFoundException(error, se); } } } boolean delegateLoad = delegate || filter(name, true); // (1) Delegate to our parent if requested if (delegateLoad) { if (log.isDebugEnabled()) { log.debug(" Delegating to parent classloader1 " + parent); } try { clazz = Class.forName(name, false, parent); if (clazz != null) { if (log.isDebugEnabled()) { log.debug(" Loading class from parent"); } if (resolve) { resolveClass(clazz); } return clazz; } } catch (ClassNotFoundException e) { // Ignore } } // (2) Search local repositories if (log.isDebugEnabled()) { log.debug(" Searching local repositories"); } try { clazz = findClass(name); if (clazz != null) { if (log.isDebugEnabled()) { log.debug(" Loading class from local repository"); } if (resolve) { resolveClass(clazz); } return clazz; } } catch (ClassNotFoundException e) { // Ignore } // (3) Delegate to parent unconditionally if (!delegateLoad) { if (log.isDebugEnabled()) { log.debug(" Delegating to parent classloader at end: " + parent); } try { clazz = Class.forName(name, false, parent); if (clazz != null) { if (log.isDebugEnabled()) { log.debug(" Loading class from parent"); } if (resolve) { resolveClass(clazz); } return clazz; } } catch (ClassNotFoundException e) { // Ignore } } } throw new ClassNotFoundException(name); }

    Tomcat优化的思路

    系统性能的衡量指标,主要是响应时间和吞吐量。

    • 响应时间:执⾏某个操作的耗时;

    • 吞吐量:系统在给定时间内能够⽀持的事务数量,单位为TPS(Transactions PerSecond的缩写,也就是事务数/秒,⼀个事务是指⼀个客户机向服务器发送请求然后服务器做出反应的过程。

    Tomcat优化要从两个⽅⾯进⾏

    • JVM虚拟机优化(优化内存模型)

    • Tomcat⾃身配置的优化(⽐如是否使⽤了共享线程池?IO模型?)

    JVM虚拟机优化主要是针对jvm参数和GC进行优化,对于GC的参数可以参考JVM中GC常用参数说明及理解GC日志

    对于GC操作的内存可以参考Java内存区域(运行时数据区域)划分

    JVM参数调整建议
    参数作⽤优化建议
    -server启动Server,以服务端模式运⾏ 服务端模式建议开启
    -Xms最⼩堆内存建议与-Xmx设置相同
    -Xmx最⼤堆内存建议设置为可⽤内存的80%
    -XX:metaspaceSize元空间初始值
    -XX:MaxmetaspaceSize元空间最⼤内存默认⽆限
    -XX:NewRatio年轻代和⽼年代⼤⼩⽐值,取值为整数,默认为2不需要修改
    -XX:SurvivorRatioEden区与Survivor区⼤⼩的⽐值,取值为整数,默认为8不需要修改

    在bin/catalina.sh文件中可以通过JAVA_OPTS调整JVM参数

    参数调整示例:

    JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:metaspaceSize=256m -
    
    XX:MaxmetaspaceSize=512m"
    

    调整后可以通过 jhsdb jmap --heap --pid tomcat端口号 来查询tomcat中jvm配置

    垃圾回收器参数建议
    参数作⽤
    -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暂停时间的⽬标。

    在bin/catalina.sh文件中可以通过JAVA_OPTS调整JVM参数

    JAVA_OPTS="-XX:+UseConcMarkSweepGC"
    
    Tomcat配置调优建议
    • 调整tomcat线程池

    • 调整tomcat的连接器

      调整tomcat/conf/server.xml 中关于链接器的配置可以提升应⽤服务器的性能。

      参数说明
      maxConnections最⼤连接数,当到达该值后,服务器接收但不会处理更多的请求, 额外的请求将会阻塞直到连接数低于maxConnections 。可通过ulimit -a 查看服务器限制。对于CPU要求更⾼(计算密集型)时,建议不要配置过⼤ ; 对于CPU要求不是特别⾼时,建议配置在2000左右(受服务器性能影响)。 当然这个需要服务器硬件的⽀持
      maxThreads最⼤线程数,需要根据服务器的硬件情况,进⾏⼀个合理的设置
      acceptCount最⼤排队等待数,当服务器接收的请求数量到达maxConnections ,此时Tomcat会将后⾯的请求,存放在任务队列中进⾏排序, acceptCount指的就是任务队列中排队等待的请求数 。 ⼀台Tomcat的最⼤的请求处理数量,是maxConnections+acceptCount
    • 禁⽤ A JP 连接器

    • 调整 IO 模式

      Tomcat8之前的版本默认使⽤BIO(阻塞式IO),对于每⼀个请求都要创建⼀个线程来处理,不适

      合⾼并发;Tomcat8以后的版本默认使⽤NIO模式(⾮阻塞式IO)

    • 动静分离

      可以使⽤Nginx+Tomcat相结合的部署⽅案,Nginx负责静态资源访问,Tomcat负责Jsp等动态资源访问处理(因为Tomcat不擅⻓处理静态资源)。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/532017.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号