Tomcat对于web开发人员来讲再熟悉不过了,它是由Apache开发的一个免费开源的Web应用服务器。在Web开发时,常常用它构建轻量级的Java Web服务。想要简单的使用Tomcat是很是容易的,可是想要深刻了解Tomcat体系必需要了解它背后的架构设计。php
本篇文章对《Tomcat内核设计剖析》这本书的阅读总结,大概的梳理了一下Tomcat的架构设计、模块组成。java
其实从Tomcat配置文件Server.xml的格式就能看出它的结构。web
<?xml version='1.0' encoding='utf-8'?>
<Server>
<Listener/>
<GlobalNamingResource>
<Resoource/>
</GlobalNamingResource>
<Service>
<Executor/>
<Connector/>
<Engine>
<Cluster/>
<Realm/>
<Host>
<Context/>
</Host>
</Engine>
</Service>
</Server>
复制代码
Server组件表明整个Tomcat容器,是Tomcat最外层的组件,它包括三个部分:生命周期监听器,全局命名资源,Service组件数据库
因为Tomcat是庞大而复杂的,而且它拥有不少组件,若是这些组件一个一个单独启动时很是麻烦的,并且容易遗漏,不易扩展其余组件。因此Tomcat提供了监听器方案,在其生命周期的不一样阶段调用监听器,若是某个组件对于某事件感兴趣,只须要实现接口便可缓存
对应Server.xml配置文件的节点就是<GlobalNamingResource>,在Tomcat初始化时经过Digester框架将其解析成对象,并以树状结构存储,它提供的命名对象经过ResourceLink给全部Web应用访问使用。如:JNDI中存储数据库链接池对象。安全
Server组件会开放一个端口用于监听命令,默认为8005.当Tomcat接口收到“SHUTDOWN”命令时则会关闭程序。bash
Server组件中能够包含多个Service组件,Service组件也是Tomcat最外层的组件之一。包含若干Connector组件和Executor组件组合而成,其中不一样的Connector组件可使用不用的通讯协议,如:HTTP,AJP。服务器
Executor组件则是Service组件下的线程池,Connector组件能够经过Executor组件实现线程池共享,默认状况下使用本身的私有线程池,其余组件也可使用。此外,Service组件还包含了一个很是重要的Engine组件,Connector组件负责接收客户端消息,而Engine组件则负责处理客户端消息。网络
Connector主要职责就是接收客户端链接并接收报文,将其解析后裔送给Engine处理。它的组件包括:Protocol组件,Mapper组件,CoyoteAdaptor组件。架构
Protocol是协议的抽象,它有不一样的实现,每个实现对应一种通讯协议,其中包括的Endpoint是接收端的抽象,分为Acceptor专门接收客户端链接的接收器组件和Executor线程池组件,Processor组件是对客户端请求处理的抽象。
Mapper是路由组件,它能够将请求分发到对应的Web应用的某个Servlet上。
而CoyoteAdaptor组价是一个适配器,它负责将Connector组件和Engine容器适配链接起来。
以Http协议来讲:
Protocol组件的HTTP实例则是Http11Protocol和HttpNIOProtocol。由IO模式的不一样,能够分为阻塞模式和非阻塞模式
HttpProtocol表示阻塞式的HTTP协议通讯,由传统Socket套接字为底层实现,它包含JIoEndpoint和Http11Processor组件
ServerSocketFactory根据不一样的安全层次如HTTP,HTTPS建立ServerSocket对象供Acceptor使用。 链接数控制器(LimitLatch)对链接数进行计数,来一个请求加一,请求结束后减一。请求数过多时就阻塞请求或拒绝处理。 Socket接收器(Acceptor),监听某个端口,当有链接时就将任务丢给任务执行器。 任务定义器(SocketProcessor),定义好任务处理的统一流程抽象,如:
{
处理套接字输出的响应报文;
链接数计数器减一;
关闭套接字;
}
复制代码
任务执行器(Executor),维护一个任务队列,不断从任务队列中取得任务,执行任务定义器定义好的任务。
Http11NIOProtocol表示非阻塞模式Http协议的通讯,它主要包含NioEndpoint组件和Http11NioProcessor组件。一个链接到来时,将被注册到NioChannel队列中,由Poller负责检测通道的读写事件,并在建立任务后扔进线程池中。
BIO模式在接收到一个请求时,将开启一个线程处理该请求,此时一个线程只能处理一个请求,而且请求并非like发送数据,因为网络或业务的一些缘由,服务端并不能马上接收到数据进行处理,而在等待数据准备的过程当中形成了资源浪费。
NIO模式克服了这种弊端,它能基于时间在一个线程中同时维护大量链接,它将套接字工做交给一个线程,读写交给其余N个线程,在接收请求后,它将请求放入Channel队列中,用轮询器不断检测通道读写事件,若是数据准备好了,可读后,则将该链接交由链接池读写处理。
NIO模式,对数据的操做读写,即调用基础的IO操做API依然是阻塞的,只有对网络IO才是非阻塞的,在等待数据时是非阻塞的。
同步非阻塞:同步时对IO来讲,非阻塞是处理方式。
Engine即为全局引擎容器,它的标准实现为StandardEngine。其中主要包括组件有Host、AccessLog、Pipeline、Cluster、Realm、LifecycleListener、Log组件。
Tomcat提供了统一的日志接口Log,并提供了国际化组件,在每一个Java包下面都会存在LocalStrings.properties的不一样语言版本。而且以Java包为单位划分范围,每一个类若是须要查找消息都到对应的java包下的properties文件中查找。
还设计了客户端访问日志记录接口,并提供了不一样的实现,对持久化方式的不一样有FileAccessLog,JDBCAccessLog等,还能够本身实现访问日志组件。
在复杂的大型系统中,存在某个对象或数据流须要进行繁杂的逻辑处理,能够采用管道模式,划分出每一个小模块互相独立且各自负责一段逻辑处理。
如Request、Response就是这种须要进行繁复加工处理的对象,采用管道在其中设置阀门处理逻辑。在Tomcat中有4个级别的容器分别是 Engine、Host、Context、Wrapper。请求对象将分别由这4个容器处理,在4个容器之间经过管道机制进行传递,请求对象先经过Engine管道,经由若干个阀门处理,最后由基础阀门处理流转到下一级通道,每一级管道都有一个基础阀门。
Tomcat提供了集群功能,能够快速的布置Web集群应用,而集群之间的一些通讯则由Cluster组件完成。Cluster组件主要功能是提供会话复制,上下文属性复制和在集群下的web应用部署,而且Cluster组件可分为两个级别Engine和Host。
在Cluster组件内部真正负责集群间通讯的是Tribes组件,它维护着一个集群内存活主机列表,当Cluster调用发送消息接口时,由Tribes组件将消息发送到集群中的其余主机上。
Realm域其实能够当作一个包含了用户及密码的数据库,根据用户角色的不一样,限制用户访问应用的url或资源信息。Realm域是为了统一web容器资源安全、管理,统一抽象重复认证工做方便web应用资源权限管理开发而提供的一个概念,它支持Engine、Host、Context级别容器的共享。
LifecycleListener便是容器生命周期状态监听器。
Host组件是Servlet引擎中虚拟主机的抽象,Engine中能够包含多个Host容器,而一个Host容器也能够包含若干个Context容器,Host容器包括的组件有Context容器、AccessLog、Pipeline、Cluster、Realm、HostConfig、Log组件
Host做为虚拟主机容器,用于放置Context级别容器,而Context其实对应的就是web应用,每一个Tomcat应用都有本身的配置,当Tomcat启动时,必须把对应web属性加载进Context中,若是在Tomcat启动时加载配置,这样对Context的配置修改不会马上生效,必须重启Tomcat。因此Tomcat采用监听器的方式,当Tomcat启动时触发“START_EVENT”事件时执行web应用部署动做。
Context容器对应一个Web应用程序,它包含若干个Wrapper组件、Realm、AccessLog、ErrorPage、Manager、DirContext、安全认证组件、JarScanner、过滤器、NamingResource、Mapper、Pipeline、WebAppLoader、ApplicationContext、InstanceManager、ServletContainerInitializer和Listeners组件。
Wrapper容器对应的就是Servlet,它内部维持了一个Servlet对象或者一个Servlet对象池。通常来讲一个Wrapper只有一个Servlet对象,因此全部处理线程都调用同一个Servlet对象,但某个Servlet实现了SingleThreadModel接口也容许多个对象存在。
request,response对象由Context基础阀门流转到Wrapper容器管道后,通过Wrapper管道的多个阀门,最后进入基础阀门,基础阀门调用过滤链,最后调用Servlet接口的service方法,对于HttpServlet实现来讲,在调用service方法后,HttpServlet在service方法内部对Method进行判断,分别调用doGet、doPost等不一样方法。
容器最重要的是对于资源的隔离,Tomcat采用自定义的类加载器完成了资源的隔离解决了如下四个问题:
- 同一个Web服务器内,各web项目之间的java类库相互隔离
- 同一个web服务器内,各web项目能够共享java类库
- 服务器不能收web项目影响,因此服务器类库应该与应用程序隔离
- 支持热重载
使用java类加载器能够动态加载须要的类,没有使用到的类不须要加载,能够节省程序运行内存。在java中咱们用彻底匹配类名来标识一个类,而在JVM中由彻底匹配类名和一个类加载器的实例Id做为惟一标识。也就是说,同一个虚拟机能够有两个包名、类名都相同的类,只要它们由两个不一样的类加载器加载。这种特征为咱们提供了隔离机制,只要对不一样的web项目用不一样的类加载器加载jar包就能够隔离开资源类库。而在热重载的时候,只须要从新建立一个类加载器替换旧的类加载器,而原来的类加载器会被gc回收,就能够不用重启Tomcat而从新加载了web应用程序。
Jasper就是JSP解析引擎,Tomcat使用Jasper解析Jsp文件,将之转化成继承HttpJspBase的类文件,而后用Eclipse JDT java编译器或Ant编译器将java文件编译成字节码文件,最后采用类加载器加载字节码文件。这也是为何Tomcat第一次访问jsp文件比较慢的缘由,它须要将jsp编译成字节码文件加载运行,在第一次加载完成后,Tomcat将它缓存下来下次就能够直接使用,而且Tomcat会在后台线程不断检测Jsp文件与编译后的字节码文件的最后修改时间是否相同,若是不相同,则表示jsp文件有改动,则须要从新编译。
Tomcat是一个十分庞大的项目,其中涉及到方方面面的知识,我根据《Tomcat内核设计剖析》这本书的介绍从宏观上了解了Tomcat的整体设计,依然某些方面理解起来十分吃力,对于Tomcat架构的细节方面依然须要时间慢慢体会,深刻挖掘。本文也是比较浅显的作了一下读书笔记,若是其中有些地方我理解错误,欢迎评论指出。
大概回想了一下,关于Tomcat架构主要就是围绕Connector和Container组件,一个是接收器,一个是处理容器,而Container组件在Tomcat中没有显示标明,它其实就是Engine容器。经过接收器监听端口链接,当有请求到来时,接收器将获取请求读取请求报文解析成Request、Response对象,以后交给Engine引擎进行处理,通过一层层处理后,接收器再将Response对象解析成响应报文返回给客户端。