当前位置 博文首页 > 努力充实,远方可期:【Tomcat】Tomcat架构组成

    努力充实,远方可期:【Tomcat】Tomcat架构组成

    作者:[db:作者] 时间:2021-08-11 09:48

    Tomcat系列文章专栏:https://blog.csdn.net/hancoder/category_11180472.html

    一、Tomcat架构

    先上一张Tomcat的顶层结构图(图A),如下:

    这里写图片描述

    层次关系为:

    Server
    	Service 多个
    		Connector 多个
    		Engine
    			Host
    				Context
    					Wrapper
    

    Tomcat中最顶层的容器是Server,代表着整个服务器,从上图中可以看出,一个Server可以包含至少一个Service,用于具体提供服务。

    Service主要包含两个部分:ConnectorContainer。从上图中可以看出 Tomcat 的心脏就是这两个组件,他们的作用如下:

    1、Connector用于处理连接相关的事情,并提供Socket与Request和Response相关的转化;
    2、Container用于封装和管理Servlet,以及具体处理Request请求;

    一个Tomcat中只有一个Server,一个Server可以包含多个Service,一个Service只有一个Container,但是可以有多个Connectors,这是因为一个服务可以有多个连接,如同时提供Http和Https链接,也可以提供向相同协议不同端口的连接,示意图如下(Engine、Host、Context下边会说到):

    这里写图片描述

    多个 Connector 和一个 Container 就形成了一个 Service,有了 Service 就可以对外提供服务了,但是 Service 还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非 Server 莫属了!所以整个 Tomcat 的生命周期由 Server 控制。

    另外,上述的包含关系或者说是父子关系,都可以在tomcat/conf/server.xml配置文件中看出(Tomcat版本为8.0)

    这里写图片描述

    详细的配置文件文件内容可以到Tomcat官网查看:http://tomcat.apache.org/tomcat-8.0-doc/index.html

    上边的配置文件,还可以通过下边的一张结构图更清楚的理解:

    这里写图片描述

    Server标签设置的端口号为8005,shutdown=”SHUTDOWN” ,表示在8005端口监听“SHUTDOWN”命令,如果接收到了就会关闭Tomcat。一个Server有一个Service,当然还可以进行配置,一个Service有多个,Service左边的内容都属于Container的,Service下边是Connector。

    1 server

    对应Server组件,逻辑上表示整个Tomcat,即整个Catalina Servlet容器。它处于Tomcat顶层,可以包含一个或多个Service层。Tomcat提供了该层接口的一个默认实现,所以通常不需要用户自己去实现。

    server.xml 是tomcat 服务器的核心配置文件,包含了Tomcat的 Servlet 容器(Catalina)的所有配置。由于配置的属性特别多,我们在这里主要讲解其中的一部分重要配置。

    tomcat是一个servlet容器,同时他是一个server,全局唯一

    Server是server.xml的根元素,用于创建一个Server实例,默认使用的实现类是org.apache.catalina.core.StandardServer

    <Server port="8005" shutdown="SHUTDOWN">
        ...
    </Server>
    

    可选参数:

    • port : Tomcat 监听的关闭服务器的端口。
    • shutdown: 关闭服务器的指令字符串,调优的时候要关闭他。

    Server内嵌的子元素为 Listener、GlobalNamingResources、Service。

    对于监听器:默认配置的5个Listener ,实现了LifecycleListener接口,是server的监听器

    <!-- 用于以日志形式输出服务器 、操作系统、JVM的版本信息-->
    <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
    
    <!-- 用于加载(服务器启动) 和 销毁 (服务器停止) APR。 如果找不到APR库, 则会输出日志, 并不影响Tomcat启动 -->
    <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
    
    <!-- 用于避免JRE内存泄漏问题 -->
    <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
    
    <!-- 用户加载(服务器启动) 和 销毁(服务器停止) 全局命名服务 -->
    <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
    
    <!-- 用于在Context停止时重建Executor 池中的线程, 以避免ThreadLocal 相关的内存泄漏 -->
    <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
    

    GlobalNamingResources 中定义了全局命名服务:

    <!-- 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>
    

    2 Service

    对应Service组件,是包含在Server层中的一个逻辑功能层。它包含一个Engine层,以及一个或多个连接器(Connector)。

    service=多个连接器+一个servlet容器

    Service组件将一个或多个Connector组件绑定到Engine层上,Connector组件侦听端口,获得用户请求,并将请求交给Engine层处理,同时把处理结果发给用户,从而实现一个特定的实际功能。Tomcat提供了Service接口的默认实现,所以通常也不需要用户定制。

    一个Server服务器,可以包含多个Service服务。

    该元素用于创建 Service 实例,默认使用 org.apache.catalina.core.StandardService。默认情况下,Tomcat 仅指定了Service 的名称, 值为 “Catalina”。Service 可以内嵌的子元素为 :Listener、Executor、Connector、Engine,其中 :

    • Listener 用于为Service添加生命周期监听器,
    • Executor用于配置Service 共享线程池,如果连接器没有指定自己的线程池,将使用这个线程池
    • Connector 用于配置Service 包含的链接器,
    • Engine 用于配置Service中链接器对应的Servlet 容器引擎。
    <Service name="Catalina">
        ...
    </Service>
    

    3 Connector

    Connector 用于创建链接器实例。默认情况下,server.xml 配置了两个链接器

    • 一个支持HTTP协议,
    • 一个支持AJP协议。

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

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    
    <Connector protocol="AJP/1.3"
               address="::1"
               port="8009"
               redirectPort="8443" />
    ----------------------------------------
    可以配置的参数参考如下
    <Connector port="8080"
               protocol="HTTP/1.1"
               executor="tomcatThreadPool" 
               maxThreads="1000"
               minSpareThreads="100" 
               acceptCount="1000" 
               maxConnections="1000" 
               connectionTimeout="20000"
               compression="on" 
               compressionMinSize="2048" 
               disableUploadTimeout="true"
               redirectPort="8443"
               URIEncoding="UTF‐8" />
    

    属性说明:

    • port: 端口号,Connector 用于创建服务端Socket 并进行监听, 以等待客户端请求链接。如果该属性设置为0,Tomcat将会随机选择一个可用的端口号给当前Connector使用。

    • executor:本连接器使用的线程池,指向<executor>标签的引用name

    • protocol : 当前Connector 支持的访问协议。 默认为 HTTP/1.1, 并采用自动切换机制选择一个基于 JAVA NIO的链接器或者基于本地APR的链接器(根据本地是否含有Tomcat的本地库判定)。

      • 如果不希望采用上述自动切换的机制, 而是明确指定协议, 可以使用以下值。

      • // 对于Http协议:
        org.apache.coyote.http11.Http11NioProtocol // 非阻塞式 Java NIO 链接器 
        org.apache.coyote.http11.Http11Nio2Protocol // 非阻塞式 JAVA NIO2 链接器
        org.apache.coyote.http11.Http11AprProtocol  // APR 链接器
        
        <Connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
                   connectionTimeout="20000"
                   redirectPort="8443" />
        ------------------
        // 对于AJP协议 
        
        org.apache.coyote.ajp.AjpNioProtocol  // 非阻塞式 Java NIO 链接器 
        org.apache.coyote.ajp.AjpNio2Protocol //非阻塞式 JAVA NIO2 链接器
        org.apache.coyote.ajp.AjpAprProtocol  //APR 链接器
        
    • connectionTimeOut : Connector 接收链接后的等待超时时间, 单位为 毫秒。 -1 表示不超时。

    • redirectPort:接收到一个请求,当前Connector 不支持SSL请求, 接收到了一个请求, 并且也符合security-constraint 约束, 需要SSL传输,Catalina自动将请求重定向到指定的端口。

    • executor: 指定共享线程池的名称, 也可以通过maxThreads、minSpareThreads等属性配置内部线程池。

    • URIEncoding : 用于指定编码URI的字符编码, Tomcat8.x版本默认的编码为 UTF-8 ,

      • Tomcat7.x版本默认为ISO-8859-1。
    3.2 Executor

    线程池在service中没有executor标签的话就使用的默认的线程池,多个连接器共享一个线程池。

    默认情况下,Service 并未添加共享线程池配置。 如果我们想添加一个线程池, 可以在下添加如下配置:

    <!--在service标签中配置线程池-->
    <Executor name="tomcatThreadPool" 
              namePrefix="catalina‐exec‐" 
              maxThreads="200" 
              minSpareThreads="100"
              maxIdleTime="60000"
              maxQueueSize="Integer.MAX_VALUE" 
              prestartminSpareThreads="false"
              threadPriority="5"
              className="org.apache.catalina.core.StandardThreadExecutor"/>
    然后在下面的connector标签中指定
    

    jconsole工具,找到本地线程的bootstrap,可以在线程里看到http-nio-exec-ID ajp-nio-exec-ID线程(原有的)。如果我们自己命名了<Executor name,那么就能看到我们自己的name-exec-线程id

    然后我们在下面的connector标签中配置好后,再查看的话会发现比如nio-exec的名字变了

    属性含义
    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接口。

    如果不配置共享线程池,那么Catalina 各组件在用到线程池时会独立创建。

    4 container四大容器

    这里写图片描述

    在后门我们将上面的组件称为E、H、C、W

    4.0 管道

    你可能听说过了tomcat有4大容器,而且每个容器有自己的管道。我把四大容器称为EHCW,他们是==父子容器==的关系。

    Tomcat里还有个组件pipeline,他有个list属性walve阀门。walve实现类有

    实现类为:

    • StandardEngineValve
    • StandardHostValve
    • StandardContextValve
    • StandardWrapperValve

    可以实现RequestFilterWalve接口自定义阀门。可以记录日志

    Container处理请求是使用Pipeline-Valve管道来处理的!(Valve是阀门之意,通过这个阀门后才进入下一个阀门)

    Pipeline-Valve是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后,在函数体的最后一句交给下一个处理着继续处理。(转入下一个pipeline处理)

    这里写图片描述

    在源码中体现为,

    请求来了之后new Request,request.setHost,req.setContext,req.setWarpper,
    
    
    // service的getPipeline().getFirst().invoke()方法
    connector.getService().getContainer().getPipeline().getFirst().invoke();
    
    // 在invoke()里最后一句又调用host的方法
    host.getPipeline().getFirst().invoke();
    
    // 在他的最后一句又调用context的方法
    context.getPipeline().getFirst().invoke();
    
    // 在invoke()里最后一句又调用wrapper的方法
    wrapper.getPipeline().getFirst().invoke();
    
    // 然后在wrapper里行过滤链FilterChain,在过滤链的doFilter()里追踪到`Servlet`的`service()`方法
    wrapper.allocate()得到servlet,
    

    4.1 Engine

    对应Engine组件,该层是请求分发处理层,可以连接多个Connector。它从Connector接收请求后,解析出可以完成用户请求的URL,根据该URL可以把请求匹配到正确的Host上,当Host处理完用户请求后,Engine层把结果返回给适合的连接器,再由连接器传输给用户。该层的接口一般不需要用户来定制,特殊情况下,用户可以通过实现该接口来提供自定义的引擎。

    实现类:StandardEngine

    Engine 作为Servlet 引擎的顶级元素,内部可以嵌入: Cluster、Listener、Realm、Valve和