百日筑基第六十天-学习一下Tomcat
一、Tomcat 顶层架构
Tomcat 中最顶层的容器是 Server,代表着整个服务器,从上图中可以看出,一个 Server可以包含至少一个 Service,用于具体提供服务。Service 主要包含两个部分:Connector 和 Container。从上图中可以看出 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 配置文件中看出。
二、简要解释下 server.xml 配置文件的信息
server.xml 是 Tomcat 中最重要的配置文件,server.xml 的每个元素都对应了 Tomcat中的一个组件;通过对xml文件中元素的配置,可以实现对 Tomcat 中各个组件的控制。
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN"><Listener className="org.apache.catalina.startup.VersionLoggerListener"/><Listener SSLEngine="on" className="org.apache.catalina.core.AprLifecycleListener"/><Listener className="org.apache.catalina.core.JasperListener"/><Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/><Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/><Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/><GlobalNamingResources><Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/></GlobalNamingResources><Service name="Catalina"><Connector port="8080" protocol="HTTP/1.1" connectionTimeOut="20000" redirectPort="8443"/><Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/><Engine defaultHost="localhost" name="Catalina"><Realm className="org.apache.catalina.realm.LockOutRealm"><Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/></Realm><Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true"><Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" prefix="localhost_access_log." suffix=".txt"/><Context docBase="cas-server-webapp" path="/cas" reloadable="true" source="org.eclipse.jst.j2ee.server:cas-server-webapp"/><Context docBase="portal" path="/portal" reloadable="true" source="org.eclipse.jst.jee.server:portal"/></Host></Engine></Service>
</Server>
server.xml 的整体架构(简洁版):
<Server><Service><Connector /><Connector /><Engine><Host><Context /><!-- 现在常常使用自动部署,不推荐配置Context元素,Context小节有详细说明 --></Host></Engine></Service>
</Server>
【1】顶层元素:<Server></Server>
:<Server>
元素是整个配置文件的根元素,<Server
>元素代表一个 Engine元素以及一组与之相连的 Connector元素。 【2】连接器:<Connector>
:<Connector>
代表了外部客户端发送请求到特定 Service的接口;同时也是外部客户端从特定Service 接收响应的接口。 【3】容器:<Engine>``<Host>``<Context>
:容器的功能是处理 Connector接收进来的请求,并产生对应的响应。Engine 包含Host,Host 包含 Context。一个 Engine组件可以处理 Service中的所有请求,一个 Host组件可以处理发向一个特定虚拟主机的所有请求,一个 Context组件可以处理一个特定 Web应用的所有请求。
三、Tomcat 都有哪些核心组件
【1】Server:Server元素在最顶层,代表整个 Tomcat容器,因此他必须是 server.xml中唯一一个最外层的元素。一个 Server元素可以有一个或多个 Service元素。
<Server port="8005" shutdown="SHUTDOWN">
</Server>
可以看到,最外层有一个 <Server>
元素,shutdown 属性表示关闭 Server的指令;port 属性表示 Server接收 shutdown指令的端口号,设置为-1可以禁掉该端口。Server 的主要任务,就是提供一个接口让客户端能够访问到这个 Service集合,同时维护它所包含的所有的 Service的生命周期,包含如何初始化,如何结束服务,如何找到客户端要访问的 Service。
【2】Service:在 Connector 和 Engine外面包一层,把它们组合在一起,对外提供服务。一个Service可以包含多个Connector,但是只能包含一个Engine;其中 Connector 的作用是从客户端接收请求,Engine 的作用是处理接收进来的请求。
<Server port="8005" shutdown="SHUTDOWN"><Service name="Catalina"></Service>
</Server>
如上图,Server 中包含一个名称为 “Catalina” 的Service。实际上,Tomcat 可以提供多个 Service,不同的 Service监听不同的端口。
【3】Connector:接收连接请求,创建 Request 和 Response对象用于和请求端交换数据;然后分配线程让 Engine来处理这个请求,并把产生的 Request 和 Response对象传给 Engine。通过配置 Connector,可以控制请求 Service的协议及端口号。
<Server port="8005" shutdown="SHUTDOWN"><Service name="Catalina"><Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /><Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /></Service>
</Server>
通过配置第一个 Connector,客户端可以通过 8080端口号协议访问 Tomcat。其中,protocol 属性规定了请求的协议,port 规定了请求的端口号,redirectPort 表示当强制要求 https而请求是 http时,重定向至端口号为 8443的Connector,connectionTimeout表示连接的超时时间。
在这个例子中,Tomcat 监听 Http请求,使用的是 8080端口,而不是正式的 80端口;实际上,在正式的生产环境中,Tomcat也常常监听8080端口。而不是80端口。这是因为在生产环境中,很少讲 Tomcat直接对外开放接收请求,而是在 Tomcat和客户端之间加一层代理服务器(如Nginx),用于请求的转发、负载均衡、处理静态文件等;通过代理服务器访问 Tomcat时,是在局域网中,因为一般仍使用8080端口。
第二个配置 Connector,客户端可以通过 8009端口使用 AJP协议访问 Tomcat。AJP协议负责和其他的Http服务器(如Apache)建立连接;在把 Tomcat与其他服务器集成时,就需要用到这个连接器,之所以使用 Tomcat和其他服务器集成,是因为 Tomcat可以用作 Servlet/JSP容器,但是对静态资源处理速度较慢,不如 Apache和 IIS等 HTTP服务器;因此常常将 Tomcat和 Apache等集成,前者做 Servlet容器,后者处理静态资源,而 AJP协议便负责 Tomcat与 Apache的连接。Tomcat 和 Apache等集成的原理如下图:
【4】Engine:Engine 组件在 Service 组件有且只有一个;Engine 是 Service组件中的请求处理组件。Engine组件从一个或多个Connector 中接收并处理,并将完成的响应返回给 Connector,最终传递给客户端。前面说到,Engine、Host 和 Context都是容器,但是它们不是平行关系,而是父子关系:Engine 包含 Host,Host 包含 Context。
<Server port="8005" shutdown="SHUTDOWN"><Service name="Catalina"><Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /><Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /><Engine name="Catalina" defaultHost="localhost"></Engine></Service>
</Server>
其中 name属性用于日志和错误信息,在整个 Server中应该是唯一的。defalutHost 属性指定了默认的 host名称,当发往本机的请求指定的 host名称不存在时,一律使用 defaultHost指定的 host进行处理;因此 defaulthost的值,必须与 Engine中的一个Host组件的 name属性值匹配。
【5】Engine 和 Host:Host 是 Engine的子容器。Engine 组件中可以内嵌1个或者多个 Host组件,每个 Host组件代表 Engine中的一个虚拟主机。Host组件至少有一个,且其中一个的 name必须与 Engine组件中的 defaultHost属性相匹配。
【6】Host 的作用:Host 虚拟主机的作用,是运行多个 Web应用(一个 Context 代表一个 Web应用),并负责安装、展开、启动、结束每个 Web应用。Host 组件代表的虚拟主机,对应服务器中一个网络名实体(如"www.test.com"或IP地址"116.25.25.25");为了使用户可以通过网络名连接Tomcat服务器,这个名字应该在DNS服务器上注册。
客户端通常使用主机名来标识它们希望连接的服务器,该主机名也会包含在 HTTP请求头中,Tomcat 从 HTTP头中提取出主机名,寻找名字匹配的主机。如果没有匹配,请求会发送至默认的主机。因此默认主机不需要再 DNS服务器中注册网络名,因为任何与所有 Host名称不匹配的请求,都会路由至默认主机。
【7】Host 的配置:name 属性指定虚拟主机的主机名,一个 Engine有且只有一个 Host组件的 name属性和 Engine组件的 defaultHost属性相匹配;一般情况下,主机名需要是在 DNS服务器中注册网络名,但是 Engine指定的 defaultHost不需要。
<Server port="8005" shutdown="SHUTDOWN"><Service name="Catalina"><Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /><Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /><Engine name="Catalina" defaultHost="localhost"><Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"></Host></Engine></Service>
</Server>
unpackWARs 指定了是否将代表 Web应用的 WAR文件解压;如果是 true,通过解压后的文件结构运行该 Web应用,如果是false,直接使用 WAR文件运行 Web应用。
【8】Context:Context元素代表在虚拟主机上运行的一个Web应用。在后文中,提到 Context、应用或 Web应用,他们都代指Web应用,每个 Web应用基于 WAR文件,或 WAR文件解压后对应的目录(这里称为应用目录)Context 是 Host 的子容器,每个 Host 都可以定义任意多的 Context元素。<Context>
元素的配置:Context 元素最重要的属性是 docBase 和 path,此外reloadable 属性也比较常用。
docBase 指定了该 Web应用使用 war包路径,或应用目录。需要注意的是:在自动部署场景下(配置文件位于 xmlBase中),docBase 不在 appBase目录中,才需要指定;如果 docBase指定的 war包或应用目录就在 appBase中,则不需要指定。因为 Tomcat会自动扫描 appBase中的 war包和应用目录,制定了反而造成问题。
path 指定了访问该Web应用上下文路径,当请求到来时,Tomcat 根据 Web应用的 path属性与 URL匹配程度来选择 Web应用处理相应请求。例如:Web 应用 app1的 path属性是"/app1",Web应用 app2的path属性是"/app2",那么请求/app1/index.html会交由app1来处理;而请求 /app2/index.html会交由 app2来处理。如果一个 Context元素的 path属性为"",那么这个Context是虚拟主机的默认的 Web应用;当请求的uri与所有的 path都不匹配时,使用该默认Web应用来处理。
但是,需要注意的是,在自动部署场景(配置文件位于xmlBase中),不能指定path属性,path属性由配置的文件的文件名,WAR文件的文件名或应用目录的名称自动推导出来。如扫描 Web应该时,发现xmlBase目录下的app1.xml,或appBase目录下的 app1.WAR或 app1应用目录,则该Web用于的path属性是"app1"。如果名称不是app1而是ROOT,则该Web应用时虚拟主机默认的Web应用,此时path属性推导为""。
reloadable属性指示tomcat是否在运行时监控在 WEB-INF/classes和WEB-INF/lib目录下class文件的改动。如果值为 true,那么当 class文件改动时,会重新 web应用的重新加载。在开发环境下,reloadable 设置为 ture便于调试;但是在生产环境中设置为 true会给服务器带来性能压力,因此reloadable参数的默认值为false。在该例子中,docBase 位于 Host的 appBase目录之外;path属性没有指定,而是根据 app1.xml自动推导为 “app1”。
<Context docBase="D:\Program Files\app1.war" reloadable="true"></Context>
若是自动部署(即 autoDeploy=“true”),那么 server.xml配置文件中没有 Context元素的配置。这是因为 Tomcat开启了自动部署,Web应用没有在 server.xml中配置静态部署,而是由 Tomcat通过特定的规则自动部署。
四、Web 的自动部署
要开启 Web应用的自动部署,需要配置所在的虚拟主机;配置的方式就是在配置 Host元素的 deployOnStartup和 autoDeploy属性。如果 deployOnStartup 和 autoDeploy设置为 true,则 tomcat启动自动部署:当检测到新的 Web应用或 Web应用更新时,会触发应用的部署(或重新部署)。二者的主要区别在于
【1】deployeOnStartup 为 true时,Tomcat在 启动时检查Web应用,且检测到所有的Web应用都试做新应用; 【2】autoDeploy为 true时,Tomcat 在运行时定期检查新的 Web应用或 Web应用的更新;
通过配置 deployOnStartup 和 autoDeploy 可以开启虚拟主机自动部署Web应用;实际上,自动部署依赖于检查是否有新的或更改过的 Web应用,而 Host元素中的 appBase和 xml配置设置了检查 Web应用更新的目录。
其中,appBase 属性指定 Web应用所在的目录,默认值是 webapps,这是一个相对路径,代表 Tomcat根目录下的 webapps文件夹。xmlBase 属性指定 Web应用的 XML配置文件所在的目录,默认值为 conf/<engine_name><engine_name>,例如上面例子中,主机 localhost 的 xmlBase的默认值是 $TOMCAT_HOME/conf/Catalina/localhost。
五、appBase 和 docBase的区别
【1】appBase:这个目录下面的子目录将自动被部署为应用,且 war文件将被自动解压缩并部署为应用,默认为 tomcat下webapps目录。
【2】docBase:指定需要关联的项目自动解压并部署到 appBase目录下。项目的名称由 path属性决定。
先部署 <Context path="" docBase="/app/tomcat/webapps/xxx">
需要注意,docBase 所在的文件或者 war包必须存在。否则项目启动找不到对应的目录。此时文件解压到 appBase目录下,根据 path属性,决定解压后的文件名。
若采用了 <Host name="localhost" appBase="webapp" autoDeploy="true">
配置,那么 appBase目录下的应用目录将会再次部署。此时项目是部署了两遍。解决办法,设置 autoDeploy=“false”。
六、Tomcat 顶层架构小结
【1】Tomcat 中只有一个Server,一个 Server可以有多个 Service,一个 Service可以有多个 Connector和一个 Container;
【2】Server 掌管着整个 Tomcat的生死大权;
【3】Service 是对外提供服务的;
【4】Connector 用于接受请求并将请求封装成 Request 和 Response来具体处理;
【5】Container 用于封装和管理 Servlet,以及具体处理 Request请求;
知道了整个 Tomcat顶层的分层架构和各个组件之间的关系以及作用,对于绝大多数的开发人员来说 Server和 Service对我们来说确实很远,而我们开发中绝大部分进行配置的内容是属于Connector 和 Container的,所以接下来介绍一下 Connector 和Container。
七、Connector 和 Container的微妙关系
由上述内容我们大致可以知道一个请求发送到 Tomcat之后,首先经过 Service然后会交给我们的 Connector,Connector 用于接收请求并将接收的请求封装为 Request 和 Response来具体处理,Request 和 Response封装完之后再交由 Container进行处理,Container 处理完请求之后再返回给 Connector,最后在由 Connector通过 Socket将处理的结果返回给客户端,这样整个请求的就处理完了!
Connector 最底层使用的是 Socket来进行连接的,Request 和 Response是按照 HTTP协议来封装的,所以 Connector同时需要实现 TCP/IP协议和 HTTP协议。Tomcat 既然处理请求,那么肯定需要先接收到这个请求,接收请求这个东西我们首先就需要看一下 Connector。
八、Container 架构分析
Container 用于封装和管理 Servlet,以及具体处理 Request请求,在Connector内部包含了4个子容器,结构图如下:
4个子容器的作用分别是:
【1】Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine;
【2】Host:代表一个站点,也可以叫虚拟主机,通过配置Host就可以添加站点;
【3】Context:代表一个应用程序,对应着平时开发的一套程序,或者一个 WEB-INF目录以及下面的 web.xml文件;
【4】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 处理请求是使用 Pipeline-Valve 管道来处理的!(Valve是阀门之意)Pipeline-Valve 是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将处理后的请求返回,再让下一个处理着继续处理。
但是!Pipeline-Valve 使用的责任链模式和普通的责任链模式有些不同!区别主要有以下两点:
【1】每个 Pipeline 都有特定的 Valve,而且是在管道的最后一个执行,这个 Valve叫做 BaseValve,BaseValve是不可删除的;
【2】在上层容器的管道的 BaseValve中会调用下层容器的管道;
我们知道 Container包含四个子容器,而这四个子容器对应的 BaseValve分别在:StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve。Pipeline的处理流程图如下:
【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的方式将结果返回给客户端。
十、tomcat 容器是如何创建 servlet类实例?用到了什么原理?
当容器启动时,会读取在 webapps目录下所有的 web应用中的 web.xml文件,然后对 xml文件进行解析,并读取 servlet注册信息。然后,将每个应用中注册的 servlet类都进行加载,并通过反射的方式实例化。(有时候也是在第一次请求时实例化)在servlet 注册时加上如果为正数,则在一开始就实例化,如果不写或为负数,则第一次请求实例化。
十一、共享 session处理
目前的处理方式有如下几种:
【1】使用 Tomcat本身的 Session复制功能。参考http://ajita.iteye.com/blog/1715312(Session复制的配置)方案的有点是配置简单,缺点是当集群数量较多时,Session 复制的时间会比较长,影响响应的效率;
【2】使用第三方来存放共享Session:目前用的较多的是使用 memcached来管理共享Session,借助于memcached-sesson-manager来进行Tomcat的Session管理。参考http://ajita.iteye.com/blog/1716320(使用MSM管理Tomcat集群session)
【3】使用黏性 session的策略:对于会话要求不太强(不涉及到计费,失败了允许重新请求下等)的场合,同一个用户的session 可以由 nginx或者 apache交给同一个 Tomcat来处理,这就是所谓的 session sticky策略,目前应用也比较多。参考:http://ajita.iteye.com/blog/1848665(tomcat session sticky)Nginx 默认不包含 session sticky模块,需要重新编译才行(windows下我也不知道怎么重新编译)优点是处理效率高多了,缺点是强会话要求的场合不合适。
十二、关于 Tomcat 的 session数目
这个可以直接从 Tomcat的 web管理界面去查看即可,或者借助于第三方工具 Lambda Probe 来查看,它相对于 Tomcat自带的管理稍微多了点功能,但也不多 ;
十三、Tomcat 一个请求的完整过程
首先 DNS 解析机器,一般是 ng服务器 ip地址,然后 ng 根据 server的配置,寻找路径为 yy/的机器列表,ip和端口。最后 选择其中一台机器进行访问。下面为详细过程:
【1】请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/1.1 Connector获得;
【2】Connector把该请求交给它所在的Service的Engine来处理,并等待来自Engine的回应;
【3】Engine 获得请求localhost/yy/index.jsp,匹配它所拥有的所有虚拟主机Host;
【4】Engine 匹配到名为 localhost 的 Host(即使匹配不到也把请求交给该 Host处理,因为该Host被定义为该 Engine的默认主机);
【5】localhost Host 获得请求 /yy/index.jsp,匹配它所拥有的所有 Context;
【6】Host 匹配到路径为 /yy的 Context(如果匹配不到就把该请求交给路径名为”“的Context去处理);
【7】path=”/yy” 的 Context获得请求 /index.jsp,在它的 mapping table中寻找对应的 servlet;
【8】Context 匹配到 URL PATTERN为 *.jsp的 servlet,对应于JspServlet类;
【9】构造 HttpServletRequest 对象和 HttpServletResponse对象,作为参数调用 JspServlet的doGet或 doPost方法;
【10】Context 把执行完了之后的 HttpServletResponse对象返回给 Host;
【11】Host把HttpServletResponse对象返回给 Engine;
【12】Engine 把 HttpServletResponse对象返回给 Connector;
【13】Connector 把 HttpServletResponse对象返回给客户 browser;
十四、Tomcat 工作模式
Tomcat是一个 JSP/Servlet容器。其作为 Servlet容器,有三种工作模式:独立的 Servlet容器、进程内的 Servlet容器和进程外的Servlet容器。进入 Tomcat 的请求可以根据 Tomcat 的工作模式分为如下两类:
【1】Tomcat 作为应用程序服务器:请求来自于前端的web服务器,这可能是 Apache, IIS, Nginx等;
【2】Tomcat 作为独立服务器:请求来自于web浏览器;
Tomcat 的工作一般分为三种:
【1】bio:传统的Java I/O操作,同步且阻塞I/O,一个线程处理一个请求,并发量高时,线程数较多,浪费资源;(已经很少有人在使用)
【2】nio:JDK1.4开始支持,同步阻塞或同步非阻塞IO,可以通过少量的线程来处理大量的请求;(从Tomcat 8版本开始默认就是这种模式)
【3】apr:以JNI的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作,从而大大地提高Tomcat对静态文件的处理性能;(企业中使用较多)
十五、如何对 Tomcat 进行优化
【1】关闭 Manager管理页面;(默认已经关闭)
【2】关闭 host-mangent管理页面;(默认已经关闭)
【3】对 Tomcat 日志进行分割;
【4】定义 Tomcat 404 错误返回的页面;
【5】对 JVM进行优化;
【6】对 Tomcat线程池进行优化;
【7】更改 Tomcat的工作的模式;