Tomcat系统架构[转载]

Tomcat顶层架构

先上一张Tomcat的顶层结构图,如下

简化清晰版

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

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

    • Connector用于处理连接相关的事情,并提供Socket与Request和Response相关的转化;

    • 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配置文件中看出,如下是删除了注释内容之后的一个完整的server.xml配置文件(Tomcat版本为8.0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

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

<Service name="Catalina">
<Connector port="8060" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
</Realm>

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Context path="" docBase="/xxx/target/bigdata-web"
debug="0" reloadable="true" crossContext="true" allowLinking="true"/>

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>
</Engine>
</Service>
</Server>

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

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

Tomcat顶层架构小结

  • (1)Tomcat中只有一个Server,一个Server可以有多个Service,一个Service可以有多个Connector和一个Container;

  • (2) Server掌管着整个Tomcat的生死大权;

  • (4)Service 是对外提供服务的;

  • (5)Connector用于接受请求并将请求封装成Request和Response来具体处理;

  • (6)Container用于封装和管理Servlet,以及具体处理request请求;

以上是整个Tomcat顶层的分层架构和各个组件之间的关系以及作用,但对于绝大多数的开发人员来说Server和Service对我们来说确实很远,而我们开发中绝大部分进行配置的内容是属于Connector和Container的,所以接下来介绍一下ConnectorContainer

Connector和Container的微妙关系

-w578

由上述内容我们大致可以知道一个请求发送到Tomcat之后,首先经过Service然后会交给我们的ConnectorConnector用于接收请求并将接收的请求封装为Request和Response来具体处理,Request和Response封装完之后再交由Container进行处理,Container处理完请求之后再返回给Connector,最后在由Connector通过Socket将处理的结果返回给客户端,这样整个请求的就处理完了!

Connector最底层使用的是Socket来进行连接的,Request和Response是按照HTTP协议来封装的,所以Connector同时需要实现TCP/IP协议和HTTP协议!

Tomcat既然处理请求,那么肯定需要先接收到这个请求,接收请求这个东西我们首先就需要看一下Connector!

Connector架构分析

Connector用于接受请求并将请求封装成Request和Response,然后交给Container进行处理,Container处理完之后在交给Connector返回给客户端。

因此,我们可以把Connector分为四个方面进行理解:

(1)Connector如何接受请求的?
(2)如何将请求封装成Request和Response的?
(3)封装完之后的Request和Response如何交给Container进行处理的?
(4)Container处理完之后如何交给Connector并返回给客户端的?

首先看一下Connector的结构图,如下所示:

Connector就是使用ProtocolHandler来处理请求的,不同的ProtocolHandler代表不同的连接类型,比如:Http11Protocol使用的是普通Socket来连接的,Http11NioProtocol使用的是NioSocket来连接的。

其中ProtocolHandler由包含了三个部件:EndpointProcessorAdapter

  • Endpoint 用来处理底层Socket的网络连接

    • Endpoint由于是处理底层的Socket网络连接,因此Endpoint是用来实现TCP/IP协议的

    • Endpoint 的抽象实现AbstractEndpoint里面定义的 AcceptorAsyncTimeout 两个内部类和一个 Handler 接口。

      Acceptor用于监听请求
      AsyncTimeout用于检查异步Request的超时
      Handler 用于处理接收到的Socket,在内部调用Processor进行处理

  • Processor 用于将Endpoint接收到的Socket封装成Request

    • Processor用来实现HTTP协议的
  • Adapter 用于将Request交给Container进行具体的处理

    • Adapter将请求适配到Servlet容器进行具体的处理

至此,我们应该很轻松的回答(1)(2)(3)的问题了,但是(4)还是不知道,那么我们就来看一下Container是如何进行处理的以及处理完之后是如何将处理完的结果返回给Connector的?

Container架构分析

Container用于封装和管理Servlet,以及具体处理Request请求,在Connector内部包含了4个子容器,结构图如下:

4个子容器的作用分别是:

  • Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine;

  • Host:代表一个站点,也可以叫虚拟主机,通过配置Host就可以添加站点;

  • Context:代表一个应用程序,对应着平时开发的一套程序,或者一个 WEB-INF 目录以及下面的 web.xml 文件;

  • Wrapper:每一Wrapper封装着一个Servlet;

下面找一个Tomcat的文件目录对照一下,如下图所示:

Context和Host的区别

Context表示一个应用,Tomcat中默认的配置下 webapps 下的每一个文件夹目录都是一个Context,其中ROOT目录中存放着主应用,其他目录存放着子应用,而整个 webapps 就是一个Host站点

我们访问应用Context的时候,如果是ROOT下的则直接使用域名就可以访问,例如:www.ledouit.com.

如果是Host(webapps)下的其他应用,则可以使用 www.ledouit.com/docs 进行访问,当然默认指定的根应用(ROOT)是可以进行设定的,只不过Host站点下默认的主应用是ROOT目录下的。

看到这里我们知道Container是什么,但是还是不知道Container是如何进行处理的以及处理完之后是如何将处理完的结果返回给Connector的?别急!下边就开始探讨一下Container是如何进行处理的!

Container如何处理请求的

Container处理请求是使用 Pipeline-Valve 管道来处理的!(Valve是阀门之意)

Pipeline-Valve责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将处理后的请求返回,再让下一个处理着继续处理。但是!Pipeline-Valve使用的责任链模式和普通的责任链模式有些不同!区别主要有以下两点:

  • 每个Pipeline都有特定的Valve,而且是在管道的最后一个执行,这个Valve叫做 BaseValveBaseValve是不可删除的

  • 在上层容器的管道的BaseValve中会调用下层容器的管道。

我们知道 Container 包含四个子容器,而这四个子容器对应的 BaseValve 分别是:

StandardEngineValveStandardHostValveStandardContextValveStandardWrapperValve

Pipeline的处理流程图如下(图D):

(1)Connector在接收到请求后会首先调用最顶层容器的Pipeline来处理,这里的最顶层容器的Pipeline就是EnginePipeline(Engine的管道);

(2)在Engine的管道中依次会执行EngineValve1、EngineValve2等等,最后会执行StandardEngineValve,在StandardEngineValve中会调用Host管道,然后再依次执行Host的HostValve1、HostValve2等,最后在执行StandardHostValve,然后再依次调用Context的管道和Wrapper的管道,最后执行到StandardWrapperValve。

(3)当执行到StandardWrapperValve的时候,会在 StandardWrapperValve中创建FilterChain,并调用其doFilter方法来处理请求,这个FilterChain包含着我们配置的与请求相匹配的Filter和Servlet,其doFilter方法会依次调用所有的Filter的doFilter方法和Servlet的service方法,这样请求就得到了处理!

(4)当所有的Pipeline-Valve都执行完之后,并且处理完了具体的请求,这个时候就可以将返回的结果交给Connector了,Connector在通过Socket的方式将结果返回给客户端。

Servlet生命周期

Tomcat优化

硬件优化

单个服务器所能提供CPU、内存、硬盘的性能对处理能力有决定性影响,所以说服务器性能好,Tomcat也不会太差

Tomcat本身优化

Tomcat 的自身参数的优化,这块很像 ApacheHttp Server。修改一下 xml 配置文件中的参数,调整最大连接数,超时等.

Connector 连接器的配置

Tomcat连接器的三种方式: bionioapr,三种方式性能差别很大,apr的性能最优, bio 的性能最差。而 Tomcat 7 使用的 Connector 默认就启用的 Apr 协议,但需要系统安装 Apr 库,否则就会使用 bio 方式。

配置文件优化

配置文件优化其实就是对 server.xml 优化,可以提大大提高 Tomcat 的处理请求的能力,下面我们来看 Tomcat 容器内的优化。

默认配置下,Tomcat 会为每个连接器创建一个绑定的线程池(最大线程数 200),服务启动时,默认创建了 5 个空闲线程随时等待用户请求。

首先,打开 ${TOMCAT_HOME}/conf/server.xml,搜索【<Executor name=”tomcatThreadPool”】,开启并调整为:

1
2
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="500" minSpareThreads="20" maxSpareThreads="50" maxIdleTime="60000"/>

注意, Tomcat 7 在开启线程池前,一定要安装好 Apr 库,并可以启用,否则会有错误报出,shutdown.sh 脚本无法关闭进程。

然后,修改<Connector …>节点,增加 executor 属性,搜索【port=”8080”】,调整为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
URIEncoding="UTF-8"
connectionTimeout="30000"
enableLookups="false"
disableUploadTimeout="false"
connectionUploadTimeout="150000"
acceptCount="300"
keepAliveTimeout="120000"
maxKeepAliveRequests="1"
compression="on"
compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain,image/gif,image/jpg,image/png"
redirectPort="8443" />

其中:

  • maxThreads : Tomcat使用线程来处理接收的每个请求,这个值表示 Tomcat 可创建的最大的线程数,默认值是 200

  • minSpareThreads:最小空闲线程数,Tomcat 启动时的初始化的线程数,表示即使没有人使用也开这么多空线程等待,默认值是 10。

  • maxSpareThreads:最大备用线程数,一旦创建的线程超过这个值,Tomcat 就会关闭不再需要的 socket 线程。

上边配置的参数,最大线程 500(一般服务器足以),要根据自己的实际情况合理设置,设置越大会耗费内存和 CPU,因为 CPU 疲于线程上下文切换,没有精力提供请求服务了,最小空闲线程数 20,线程最大空闲时间 60 秒,当然允许的最大线程连接数还受制于操作系统的内核参数设置,设置多大要根据自己的需求与环境。当然线程可以配置在“tomcatThreadPool”中,也可以直接配置在“Connector”中,但不可以重复配置

  • URIEncoding:指定 Tomcat 容器的 URL 编码格式,语言编码格式这块倒不如其它 WEB 服务器软件配置方便,需要分别指定。

  • connnectionTimeout: 网络连接超时,单位:毫秒,设置为 0 表示永不超时,这样设置有隐患的。通常可设置为 30000 毫秒,可根据检测实际情况,适当修改。

  • enableLookups: 是否反查域名,以返回远程主机的主机名,取值为:true 或 false,如果设置为false,则直接返回IP地址,为了提高处理能力,应设置为 false。

  • disableUploadTimeout:上传时是否使用超时机制。

  • connectionUploadTimeout:上传超时时间,毕竟文件上传可能需要消耗更多的时间,这个根据你自己的业务需要自己调,以使Servlet有较长的时间来完成它的执行,需要与上一个参数一起配合使用才会生效。

  • acceptCount:指定当所有可以使用的处理请求的线程数都被使用时,可传入连接请求的最大队列长度,超过这个数的请求将不予处理,默认为100个。

  • keepAliveTimeout:长连接最大保持时间(毫秒),表示在下次请求过来之前,Tomcat保持该连接多久,默认是使用 connectionTimeout 时间,-1 为不限制超时。

  • maxKeepAliveRequests:表示在服务器关闭之前,该连接最大支持的请求数。超过该请求数的连接也将被关闭,1表示禁用,-1表示不限制个数,默认100个,一般设置在100~200之间。

  • compression:是否对响应的数据进行 GZIP 压缩,off:表示禁止压缩;on:表示允许压缩(文本将被压缩)、force:表示所有情况下都进行压缩,默认值为off,压缩数据后可以有效的减少页面的大小,一般可以减小1/3左右,节省带宽。

  • compressionMinSize:表示压缩响应的最小值,只有当响应报文大小大于这个值的时候才会对报文进行压缩,如果开启了压缩功能,默认值就是2048。

  • compressableMimeType:压缩类型,指定对哪些类型的文件进行数据压缩。

  • noCompressionUserAgents=”gozilla, traviata”: 对于以下的浏览器,不启用压缩。

如果已经对代码进行了动静分离,静态页面和图片等数据就不需要Tomcat处理了,那么也就不需要配置在 Tomcat 中配置压缩了。

以上是一些常用的配置参数属性,当然还有好多其它的参数设置,还可以继续深入的优化,HTTP Connector 与 AJP Connector 的参数属性值,可以参考官方文档的详细说明:
https://tomcat.apache.org/tomcat-7.0-doc/config/http.html

禁用AJP连接器

AJP(Apache JServer Protocol)AJPv13协议是面向包的。WEB服务器和Servlet容器通过TCP连接来交互;为了节省SOCKET创建的昂贵代价,WEB服务器会尝试维护一个永久TCP连接到servlet容器,并且在多个请求和响应周期过程会重用连接。如图:

在Nginx+tomcat的架构中,禁用AJP连接器:

JVM优化

根据服务器物理内容情况配置相关参数优化tomcat性能。

当应用程序需要的内存超出堆的最大值时虚拟机就会提示内存溢出,并且导致应用服务崩溃。因此一般建议堆的最大值设置为可用内存的最大值的80%。 Tomcat默认可以使用的内存为128MB,在较大型的应用项目中,这点内存是不够的,需要调大.

Unix下,在文件/bin/catalina.sh的前面,增加如下设置:

JAVA_OPTS=’-Xms【初始化内存大小】 -Xmx【可以使用的最大内存】 -XX:PermSize=64M -XX:MaxPermSize=128m’

需要把几个参数值调大。例如: JAVA_OPTS=’-Xms256m -Xmx512m’ 表示初始化内存为256MB,可以使用的最大内存为512MB。

参数详解:

1
2
3
4
5
6
-server  启用jdk 的 server 版;
-Xms java虚拟机初始化时的最小内存;
-Xmx java虚拟机可使用的最大内存;
-XX:PermSize 内存永久代保留区域
-XX:MaxPermSize 内存最大永久代保留区域
-Xmn jvm最小内存

原文链接: 四张图带你了解Tomcat系统架构–让面试官颤抖的Tomcat回答系列