Tomcat 体系架构
什么是Web服务器?
Web服务器的定义
没有标准定义。一般认为,Web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,提供网上信息浏览服务。
Web服务器的特点
- 服务器是一种被动程序:只有当Internet上运行其他计算机中的浏览器发出的请求时,服务器才会响应。
- 服务器一般使用HTTP(超文本传输协议)与客户机浏览器进行信息交流,这就是人们常把它们称为HTTP服务器的原因。
- Web服务器不仅能够存储信息,还能在用户通过Web浏览器提供的信息的基础上运行脚本和程序。
什么是Tomcat
Tomcat是一款开源轻量级Web应用服务器,是一款优秀的Servlet容器实现。
Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容。
Servlet严格来讲是指Java语言实现的一个接口,一般情况下我们说的Servlet是指任何实现了这个Servlet接口的类。
- 实例化并调用init()方法初始化该 Servlet,一般 Servlet 只初始化一次(只有一个对象)
- service()(根据请求方法不同调用doGet() 或者 doPost(),此外还有doHead()、doPut()、doTrace()、doDelete()、doOptions()、destroy())。
- 当 Server 不再需要 Servlet 时(一般当 Server 关闭时),Server 调用 Servlet 的 destroy() 方法。
典型的Servlet的处理流程:
- 第一个到达服务器的 HTTP 请求被委派到 Servlet 容器。
- Servlet 容器在调用 service() 方法之前加载 Servlet。
- 然后 Servlet 容器处理由多个线程产生的多个请求,每个线程执行一个单一的 Servlet 实例的 service() 方法。
Tomcat版本介绍
Servlet 2.X:项目目录结构必须要有WEB-INF,web.xml等文件夹和文件,在web.xml中配置servlet,filter,listener,以web.xml为java web项目的统一入口
Servlet 3.X:项目中可以不需要WEB-INF,web.xml等文件夹和文件,在没有web.xml文件的情况下,通过注解实现servlet,filter, listener的声明,当使用注解时,容器自动进行扫描。
同时Tomcat8.5进行了大量的代码重构,对比与7.0的版本,也符合Tomcat未来的代码架构体系。但是Tomcat的核心和主体架构还是一直保持这样的。
8.5版本特点
- 支持Servlet3.1
- 默认采用NIO,移除BIO
- 支持NIO2(AIO)
- 支持HTTP 2.0协议
- 默认采用异步日志处理
Tomcat启动方式
-
一般启动
Windows下:startup.bat
Linux下:startup.sh -
IDE中启动
-
嵌入式启动
启动SpringBoot -
Debug启动
在项目发布后,我们有时候需要对基于生产环境部署的应用进行调试,以解决在开发环境无法重现的BUG。这时我们就需要用到应用服务器的远程调试功能,这个主要是基于JDK提供的JPDA(Java Platform Debugger Architecture,Java平台调试体系结构)。
Tomcat项目部署
隐式部署
直接丢文件夹、war、jar到webapps目录,tomcat会根据文件夹名称自动生成虚拟路径,简单,但是需要重启Tomcat服务器,包括要修改端口和访问路径的也需要重启。
显式部署
添加context元素
config/server.xml中的Host加入一个Context(指定路径和文件地址),例如:
<Host name=“localhost”>
<Context path="/index" docBase=“F:\work_space\xxxxx.war” />
</Host>
即"/index"这个虚拟路径映射到了"F:\work_space\xxxx"目录下(war会解压成文件),修改完servler.xml需要重启tomcat 服务器。
创建xml文件
conf/Catalina/localhost中创建xml文件,访问路径为文件名,例如:
在localhost目录下新建demo.xml,内容为:
<Context docBase=“F:\work_space\demo” />
不需要写path,虚拟目录就是文件名demo,path默认为/demo,添加demo.xml不需要重启 tomcat服务器。
三种方式比较
- 隐式部署:可以很快部署,需要人手动移动Web应用到webapps下,在实际操作中不是很人性化
- 添加context元素 : 配置速度快,需要配置两个路径,如果path为空字符串,则为缺省配置,每次修改server.xml文件后都要重新启动Tomcat服务器,重新部署。
- 创建xml文件::服务器后台会自动部署,修改一次后台部署一次,不用重复启动Tomcat服务器,该方式显得更为智能化。
Tomcat目录结构
bin
-
startup,主要是检查 catalina 的执行所需环境,并调用 catalina,启动 tomcat。
-
catalina,真正启动 tomcat,可以在里面设置JVM参数。
-
shutdown,关闭 tomcat。
-
version:查看当前tomcat的版本号,
-
configtest:校验tomcat配置文件server.xml的格式、内容等是否合法、正确。
-
service:安装tomcat服务,可用net start tomcat 启动
-
脚本version.sh、startup.sh、shutdown.sh、configtest.sh都是对catalina.sh的包装,内容大同小异,差异在于功能介绍和调用catalina.sh时的参数不同。
config
- server.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- Server代表一个 Tomcat 实例。可以包含一个或多个 Service,其中每个Service有自己的一个Engine和一个或多个Connector。
port="8005"指定一个端口,这个端口负责监听关闭tomcat的请求
-->
<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" />
<!-- 全局命名资源,定义了UserDatabase的一个JNDI(java命名和目录接口),通过pathname的文件得到一个用户授权的内存数据库 -->
<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它包含一个<Engine>元素,以及一个或多个<Connector>,这些Connector元素共享用同一个Engine元素 -->
<Service name="Catalina">
<!--
每个Service可以有一个或多个连接器<Connector>元素,
第一个Connector元素定义了一个HTTP Connector,它通过8080端口接收HTTP请求;第二个Connector元素定
义了一个JD Connector,它通过8009端口接收由其它服务器转发过来的请求.
-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<!-- 每个Service只能有一个<Engine>元素 -->
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<!-- 默认host配置,有几个域名就配置几个Host,但是这种只能是同一个端口号 -->
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<!-- Tomcat的访问日志,默认可以关闭掉它,它会在logs文件里生成localhost_access_log的访问日志 -->
<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>
- web.xml
Tomcat中所有应用默认的部署描述文件,主要定义了基础的Servlet和MIME(Multipurpose Internet Mail Extensions)映射(mime-mapping 文件类型,其实就是Tomcat处理的文件类型),如果部署的应用中不包含web.xml,那么Tomcat将使用此文件初始化部署描述,反之,Tomcat会在启动时将默认描述与定义描述配置进行合并。
加载一些tomcat内置的servlet:
DefaultServlet默认的,加载静态文件 html,js,jpg等静态文件。
JspServlet专门处理jsp。 - context.xml
用于自定义所有Web应用均需要加载的Context配置,如果Web应用指定了自己的context.xml,那么该文件的配置将被覆盖。
context.xml 与 server.xml中配置 context 的区别
server.xml是不可动态重加载的资源,服务器一旦启动了以后,要修改这个文件,就得重启服务器才能重新加载。而context.xml文件则不然,Tomcat服务器会定时去扫描这个文件。一旦发现文件被修改(时间戳改变了),就会自动重新加载这个文件,而不需要重启服务器。 - catalina.policy
权限相关 Permission ,Tomcat是跑在JVM上的,所以有些默认的权限 - tomcat-users.xml
配置Tomcat的server的manager信息
- logging.properties
设置Tomcat日志。
webapps
存放web项目的目录,其中每个文件夹都是一个项目。
Tomcat自带一些项目。其中ROOT是一个特殊的项目,在地址栏中没有给出项目目录时,对应的就是ROOT项目。http://localhost:8080/examples,进入示例项目。
lib
Tomcat的类库。如果需要添加Tomcat依赖的jar文件,可以把它放到这个目录中。
- work
运行时生成的文件,最终运行的文件都在这里。通过webapps中的项目生成的。可以把这个目录下的内容删除,再次运行时会生再次生成work目录。当客户端用户访问一个JSP文件时,Tomcat会通过JSP生成Java文件,然后再编译Java文件生成class文件,生成的java和class文件都会存放到这个目录下。 - temp
存放Tomcat的临时文件,这个目录下的东西可以在停止Tomcat后删除。 - logs
这个目录中都是日志文件,记录了Tomcat启动和关闭的信息,如果启动Tomcat时有错误,那么异常也会记录在日志文件中。
localhost-xxx.log:Web应用的内部程序日志,建议保留
catalina-xxx.log:控制台日志
host-manager.xxx.log:Tomcat管理页面中的host-manager的操作日志,建议关闭
localhost_access_log_xxx.log:用户请求Tomcat的访问日志(这个文件在conf/server.xml里配置),建议关闭
Tomcat组件及架构
Server
Server是最顶级的组件,它代表Tomcat的运行实例,它掌管着整个Tomcat的生死大权(与上面的server.xml对应)。
- 提供了监听器机制,用于在Tomcat整个生命周期中对不同时间进行处理
- 提供Tomcat容器全局的命名资源实现,JNDI
- 监听某个端口以接受SHUTDOWN命令,用于关闭Tomcat
Service
一个概念,一个Service维护多个Connector和一个Container
Connector
链接器:监听转换Socket请求,将请求交给Container处理,支持不同协议以及不同的I/O方式。
Connector链接器封装了底层的网络请求(Socket请求及相应处理),提供了统一的接口,使Container容器与具体的请求协议以及I/O方式解耦。
Connector将Socket输入转换成Request对象,交给Container容器进行处理。处理请求后,Container通过Connector提供的Response对象将结果写入输出流。
因为无论是Request对象还是Response对象都没有实现Servlet规范对应的接口,Container会将它们进一步分装成ServletRequest和ServletResponse。
Container
表示能够执行客户端请求并返回响应的一类对象,其中有不同级别的容器:Engine、Host、Context、Wrapper
- Engine
整个Servler引擎,最高级的容器对象- Host
表示Servlet引擎中的虚拟机,主要与域名有关,一个服务器有多个域名是可以使用多个Host- Context
用于表示ServletContext,一个ServletContext表示一个独立的Web应用- Wrapper
用于表示Web应用中定义的Servlet
- Wrapper
- Context
- Host
Executor
Tomcat组件间可以共享的线程池
Tomcat处理一个HTTP请求的过程
用户点击网页内容,请求被发送到本机端口8080,被在那里监听的Coyote HTTP/1.1 Connector获得。
Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应。
Engine获得请求localhost/test/index.jsp,匹配所有的虚拟主机Host。
Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机),名为localhost的Host获得请求/test/index.jsp,匹配它所拥有的所有的Context。Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为“ ”的Context去处理)。
path=“/test”的Context获得请求/index.jsp,在它的mapping table中寻找出对应的Servlet。Context匹配到URL PATTERN为*.jsp的Servlet,对应于JspServlet类。
构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet()或doPost().执行业务逻辑、数据存储等程序。
Context把执行完之后的HttpServletResponse对象返回给Host。
Host把HttpServletResponse对象返回给Engine。
Engine把HttpServletResponse对象返回Connector。
Connector把HttpServletResponse对象返回给客户Browser。
Connector配置详解
以下是server.xml中的一段:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
什么是AJP?
Tomcat最主要的功能是提供Servlet/JSP容器。但在对静态资源(如HTML文件或图像文件)的处理速度,以及提供的Web服务器管理功能方面都不如其他专业的HTTP服务器,如IIS和Apache服务器。
因此在实际应用中,常常把Tomcat与其他HTTP服务器集成。对于不支持Servlet/JSP的HTTP服务器,可以通过Tomcat服务器来运行Servlet/JSP组件。
AJP链接器可以让Tomcat通过AJP协议和另一个web容器进行交互。
HTTP/1.1下配置IO
对于I/0选择,要根据业务场景来定。一般高并发场景下,APR和AIO的性能要优于NIO,(linux操作系统并没有真正实现AIO,所以一般linux上推荐使用NIO。如果是APR的话,需要安装APR库,而Windows上默认安装了)。
APR(Apache Portable Runtime):
- NIO(默认):
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
connectionTimeout="20000"
redirectPort="8443" />
- AIO(Windows推荐):
<Connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
connectionTimeout="20000"
redirectPort="8443" />
使用线程池:
server.xml 已经写好了如何配置,只需要取消注释就好了。
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
maxThreads="150" >
</Connector>
- APR:
server.xml 已经写好了如何配置,只需要取消注释就好了。
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
maxThreads="150" SSLEnabled="true" >
<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
<SSLHostConfig>
<Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
certificateFile="conf/localhost-rsa-cert.pem"
certificateChainFile="conf/localhost-rsa-chain.pem"
type="RSA" />
</SSLHostConfig>
</Connector>
Linux如果安装了apr和native,Tomcat直接启动就支持APR。(安装方法:http://my.oschina.net/lsw90/blog/181161)
连接数问题
NIO:
最大连接数:10000
线程池:200
队列长度:100
1个线程 Acceptor 负责接收连接 Channel 到 accept 队列。2个线程 Poller 负责轮询。轮询到就绪的读/写创建 SocketProcessor 交给线程池 Executor 去执行。在 Executor 的线程中,会完成从 socket 中读取 HttpRequest 解析成 HttpServletRequest 对象,分派到相应的 servlet 并完成逻辑,然后将 Response 通过 socket 发回 client。在从 socket 中读数据和往 socket中写数据的过程,并没有像典型的非阻塞的 NIO 的那样,注册 OP_READ 或 OP_WRITE 事件到主 Selector,而是直接通过 socket 完成读写,这时是阻塞完成的。