Tomcat系统架构

Tomcat总体架构

Tomcat实现2个核心功能:1处理Socket连接,负责网络字节流与Request和Response对象的转化。2加载和管理Servlet,以及具体处理Request请求。

Tomcat设计了两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情。连接器负责对外交流,容器负责内部处理。


Tomcat支持的I/O模型有:

  1. NIO:非阻塞I/O,采用Java NIO类库实现。
  2. NIO2:异步I/O,采用JDK 7最新的NIO2类库实现。
  3. APR:采用Apache可移植运行库实现,是C/C++编写的本地库。

Tomcat支持的应用层协议有:

  1. HTTP/1.1:这是大部分Web应用采用的访问协议。
  2. AJP:用于和Web服务器集成(如Apache)。
  3. HTTP/2:HTTP 2.0大幅度的提升了Web性能。

Tomcat为了实现支持多种I/O模型和应用层协议,一个容器可能对接多个连接器,单独的连接器或者容器都不能对外提供服务,需要把它们组装起来才能工作,组装后这个整体叫作Service组件。Service本身没有做什么重要的事情,只是在连接器和容器外面多包了一层,把它们组装在一起。通过在Tomcat中配置多个Service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。

最顶层是Server,指的是一个Tomcat实例。一个Server中有一个或多个Service,一个Service中有多个连接器和一个容器。连接器与容器之间通过标准的ServletRequest和ServletResponse通信。

连接器

连接器对Servlet容器屏蔽了协议及I/O模型等的区别,无论是HTTP还是AJP,在容器中获取到的都是一个标准的ServletRequest对象。

连接器功能需求:

  1. 监听网络端口。
  2. 接受网络连接请求。
  3. 读取请求网络字节流。
  4. 根据具体应用层协议(HTTP/AJP)解析字节流,生成统一的Tomcat Request对象。
  5. 将Tomcat Request对象转成标准的ServletRequest。
  6. 调用Servlet容器,得到ServletResponse。
  7. 将ServletResponse转成Tomcat Response对象。
  8. 将Tomcat Response转成网络字节流。
  9. 将响应字节流写回给浏览器。

连接器需要完成高内聚(相关度比较高的功能要尽可能集中,不要分散)的功能:

  1. 网络通信。
  2. 应用层协议解析。
  3. Tomcat Request/Response与ServletRequest/ServletResponse的转化。

Tomcat的设计者设计了3个组件来实现这3个功能,分别是EndPointProcessorAdaptor

EndPoint负责提供字节流给Processor,Processor负责提供Tomcat Request对象给Adaptor,Adaptor负责提供ServletRequest对象给容器。

I/O模型和应用层协议可以自由组合,比如NIO + HTTP或者NIO2 + AJP。Tomcat的设计者将网络通信和应用层协议解析放在一起考虑,设计了一个叫ProtocolHandler的接口来封装这两种变化点。各种协议和通信模型的组合有相应的具体实现类。比如:Http11NioProtocol和AjpNioProtocol。

系统也存在一些相对稳定的部分,Tomcat设计了一系列抽象基类来封装这些稳定的部分,抽象基类AbstractProtocol实现了ProtocolHandler接口。每一种应用层协议有自己的抽象基类,比如AbstractAjpProtocol和AbstractHttp11Protocol,具体协议的实现类扩展了协议层抽象基类。

ProtocolHandler组件

EndPoint

EndPoint是通信端点,即通信监听的接口,是具体的Socket接收和发送处理器,是对传输层的抽象,因此EndPoint是用来实现TCP/IP协议的。

EndPoint是一个接口,它的抽象实现类AbstractEndpoint里面定义了两个内部类:AcceptorSocketProcessor

Acceptor用于监听Socket连接请求。SocketProcessor用于处理接收到的Socket请求,它实现Runnable接口,在Run方法里调用协议处理组件Processor进行处理。为了提高处理能力,SocketProcessor被提交到线程池来执行。而这个线程池叫作执行器(Executor)

Processor

Processor用来实现HTTP协议,Processor接收来自EndPoint的Socket,读取字节流解析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理,Processor是对应用层协议的抽象。

Processor是一个接口,定义了请求的处理等方法。它的抽象实现类AbstractProcessor对一些协议共有的属性进行封装,没有对方法进行实现。具体的实现有AJPProcessor、HTTP11Processor等,这些具体实现类实现了特定协议的解析方法和请求处理方式。

EndPoint接收到Socket连接后,生成一个SocketProcessor任务提交到线程池去处理,SocketProcessor的Run方法会调用Processor组件去解析应用层协议,Processor通过解析生成Request对象后,会调用Adapter的Service方法。

Adapter组件

由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat定义了自己的Request类来“存放”这些请求信息。ProtocolHandler接口负责解析请求并生成Tomcat Request类。但是这个Request对象不是标准的ServletRequest,也就意味着,不能用Tomcat Request作为参数来调用容器。

Tomcat引入CoyoteAdapter,这是适配器模式的经典运用,连接器调用CoyoteAdapter的Sevice方法,传入的是Tomcat Request对象,CoyoteAdapter负责将Tomcat Request转成ServletRequest,再调用容器的Service方法。

如果连接器直接创建ServletRequest和ServletResponse对象的话,就和Servlet协议耦合了,设计者认为连接器尽量保持独立性,它不一定要跟Servlet容器工作的。对象转化的性能消耗比较少,Tomcat对HTTP请求体采取了延迟解析的策略,TomcatRequest对象转化成ServletRequest的时候,请求体的内容都还没读取,直到容器处理这个请求的时候才读取的。

容器

Tomcat设计了四种容器,分别是Engine、Host、Context和Wrapper。容器是父子关系,Tomcat通过一种分层架构使得Servlet容器具有很好的灵活性。

Context表示一个Web应用程序Wrapper表示一个Servlet,一个Web应用程序中可能会有多个Servlet;

Host代表的是一个虚拟主机,或者说一个站点,可以给Tomcat配置多个虚拟主机地址,而一个虚拟主机下可以部署多个Web应用程序;

Engine表示引擎,用来管理多个虚拟站点,一个Service最多只能有一个Engine。

Tomcat的server.xml配置文件:

Tomcat通过组合模式来管理这些容器,所有容器组件都实现了Container接口,组合模式可以使得用户对单容器对象和组合容器对象的使用具有一致性。

请求定位Servlet的过程

Tomcat通过Mapper组件来确定请求是由哪个Wrapper容器中的Servlet来处理的。Mapper组件的功能就是将用户请求的URL定位到一个Servlet,工作原理是:Mapper组件里保存了Web应用的配置信息,即容器组件与访问路径的映射关系,如Host容器里配置的域名、Context容器里的Web应用路径,以及Wrapper容器里Servlet映射的路径,这些配置信息就是一个多层次的Map。当一个请求到来时,Mapper组件通过解析请求URL里的域名和路径,再到自己保存的Map里去查找,就能定位到一个Servlet。一个请求URL最后只会定位到一个Wrapper容器,也就是一个Servlet。

用户访问一个URL,http://uer.shoping.com:8080/order/buy。经过一下几步定位到一个Servlet。

  1. 根据协议和端口号选定Service和Engine,Tomcat每个连接器都监听不同的端口,如Tomcat默认HTTP连接器监听8080端口,AJP连接器监听8009端口。URL访问的是8080端口,该请求会被HTTP连接器接收,连接器确定Service组件也就确定,Engine也就确定。
  2. 根据域名选定Host。Service和Engine确定后,Mapper组件通过URL中的域名去查找相应的Host容器,URL访问的域名是user.shopping.com,Mapper会找到Host2这个容器。
  3. 根据URL路径找到Context组件。Host确定以后,Mapper根据URL的路径来匹配相应的Web应用的路径,访问/order,找到Context4这个Context容器。
  4. 根据URL路径找到Wrapper(Servlet)。Context确定后,Mapper再根据web.xml中配置的Servlet映射路径来找到具体的Wrapper和Servlet。

Pipeline-Valve

并不是只有Servlet才会去处理请求,这个查找路径上的父子容器都会对请求做一些处理。连接器中的Adapter会调用容器的Service方法来执行Servlet,最先拿到请求的是Engine容器,Engine容器对请求做一些处理后,会把请求传给自己子容器Host继续处理,依次类推,最后这个请求会传给Wrapper容器,Wrapper会调用最终的Servlet来处理。该过程使用Pipeline-Valve管道。Pipeline-Valve是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将再调用下一个处理者继续处理。

Valve是一个处理点invoke方法就是来处理请求的。

Pipeline中维护了Valve链表,Valve可以插入到Pipeline中,Pipeline中没有invoke方法,因为整个调用链的触发时Valve来完成的,Valve完成自己的处理后,调用getNext.invoke()来触发下一个Valve调用。

不同容器的Pipeline是通过getBasic方法来调用BasicValve,其位于Valve链表的末端是Pipeline中必不可少的一个Valve,负责调用下层容器的Pipeline里的第一个Valve。

Wrapper容器的最后一个Valve会创建一个Filter链,并调用doFilter()方法,最终会调到Servlet的service方法。

Valve和Filter的区别:

  1. Valve是Tomcat的私有机制,与Tomcat的基础架构API是紧耦合的。ServletAPI是公有的标准,所有的Web容器包括Jetty都支持Filter机制。
  2. Valve工作在Web容器级别,拦截所有应用的请求;而Servlet Filter工作在应用级别,只能拦截某个Web应用的所有请求。如果想做整个Web容器的拦截器,必须通过Valve来实现。