深入理解Tomcat 基本架构

2019/06/04

The Apache Tomcat® software is an open source implementation of the Java Servlet, JavaServer Pages, Java Expression Language and Java WebSocket technologies. The Java Servlet, JavaServer Pages, Java Expression Language and Java WebSocket specifications are developed under the Java Community Process.

前言

互联网的本质,是数据迁移的过程。

以Java常用框架体系为例。

浏览器请求->Servlet容器->Restful框架->ORM框架->DB。

整个数据流,基本是这个过程。

各阶段常用的框架:

  • Servlet容器
    • tomcat
    • jetty
    • undertow
  • Restful风格框架
    • springboot
    • Jersey
  • ORM
    • hibernate
    • mybatis
    • tk. mybatis
    • mybatis-plus
    • springboot jpa
  • DB
    • mysql

还有一些中间件:rabbitMQ、Redis。

本文在深入拆解Tomcat & Jetty 的基础上,总结学习。

基本原理

Servlet接口和Servlet容器这一整套规范叫作Servlet规范。

Tomcat和Jetty都按照Servlet规范的要求实现了Servlet容器,同时它们也具有HTTP服务器的功能。

Tomcat要实现2个核心功能:

  • 处理Socket连接,负责网络字节流与Request和Response对象的转化。

  • 加载和管理Servlet,以及具体处理Request请求。

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

最顶层是Server,这里的Server指的就是一个Tomcat实例。

一个Server中有一个或者多个Service,一个Service中有多个连接器和一个容器。

连接器与容器之间通过标准的ServletRequest和ServletResponse通信。

Tomcat支持的I/O模型有:

  • NIO:非阻塞I/O,采用Java NIO类库实现。

  • NIO.2:异步I/O,采用JDK 7最新的NIO.2类库实现。

  • APR:采用Apache可移植运行库实现,是C/C++编写的本地库。

Tomcat支持的应用层协议有:

  • HTTP/1.1:这是大部分Web应用采用的访问协议。

  • AJP:用于和Web服务器集成(如Apache)。

  • HTTP/2:HTTP 2.0大幅度的提升了Web性能。

连接器 Connector

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

  • Endpoint:负责网络通信,提供字节流给Processor。

  • Processor:应用层协议解析,负责提供Tomcat Request对象给Adapter。

  • Adapter:Tomcat Request/Response与ServletRequest/ServletResponse的转化。

由于I/O模型和应用层协议可以自由组合,比如NIO + HTTP或者NIO.2 + AJP。Tomcat的设计者将网络通信和应用层协议解析放在一起考虑,设计了一个叫ProtocolHandler的接口来封装这两种变化点。

除了这些变化点,系统也存在一些相对稳定的部分,因此Tomcat设计了一系列抽象基类来封装这些稳定的部分,抽象基类AbstractProtocol实现了ProtocolHandler接口。

每一种应用层协议有自己的抽象基类,具体协议的实现类扩展了协议层抽象基类。

连接器模块有三个核心组件:Endpoint、Processor和Adapter。

其中Endpoint和Processor放在一起抽象成了ProtocolHandler组件。

ProtocolHandler组件

连接器用ProtocolHandler来处理网络连接和应用层协议。

Endpoint

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

Endpoint是一个接口,对应的抽象实现类是AbstractEndpoint,而AbstractEndpoint的具体子类,比如在NioEndpoint和Nio2Endpoint中,有两个重要的子组件:Acceptor和SocketProcessor。

其中Acceptor用于监听Socket连接请求。SocketProcessor用于处理接收到的Socket请求,它实现Runnable接口,在run方法里调用协议处理组件Processor进行处理。为了提高处理能力,SocketProcessor被提交到线程池来执行。而这个线程池叫作执行器(Executor),我在后面的专栏会详细介绍Tomcat如何扩展原生的Java线程池。

Processor

如果说Endpoint是用来实现TCP/IP协议的,那么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方法。

容器

Tomcat设计了4种容器,分别是Engine、Host、Context和Wrapper。

这4种容器不是平行关系,而是父子关系。

Tomcat通过一种分层的架构,使得Servlet容器具有很好的灵活性。

  • Context表示一个Web应用程序;
  • Wrapper表示一个Servlet,一个Web应用程序中可能会有多个Servlet;
  • Host代表的是一个虚拟主机,或者说一个站点,可以给Tomcat配置多个虚拟主机地址,而一个虚拟主机下可以部署多个Web应用程序;
  • Engine表示引擎,用来管理多个虚拟站点,一个Service最多只能有一个Engine。

Tomcat采用了组件化的设计,它的构成组件都是可配置的,其中最外层的是Server,其他组件按照一定的格式要求配置在这个顶层容器中。

请求定位Servlet的过程

Mapper组件

Mapper组件的功能就是将用户请求的URL定位到一个Servlet。

它的工作原理是: Mapper组件里保存了Web应用的配置信息,其实就是容器组件与访问路径的映射关系,比如Host容器里配置的域名、Context容器里的Web应用路径,以及Wrapper容器里Servlet映射的路径,你可以想象这些配置信息就是一个多层次的Map。

当一个请求到来时,Mapper组件通过解析请求URL里的域名和路径,再到自己保存的Map里去查找,就能定位到一个Servlet。请你注意,一个请求URL最后只会定位到一个Wrapper容器,也就是一个Servlet。

案例

假如有一个网购系统,有面向网站管理人员的后台管理系统,还有面向终端客户的在线购物系统。

这两个系统跑在同一个Tomcat上,为了隔离它们的访问域名,配置了两个虚拟域名:manage.shopping.com和user.shopping.com,

网站管理人员通过manage.shopping.com域名访问Tomcat去管理用户和商品,而用户管理和商品管理是两个单独的Web应用。

终端客户通过user.shopping.com域名去搜索商品和下订单,搜索功能和订单管理也是两个独立的Web应用。

针对这样的部署,Tomcat会创建一个Service组件和一个Engine容器组件,

在Engine容器下创建两个Host子容器,在每个Host容器下创建两个Context子容器。由于一个Web应用通常有多个Servlet,Tomcat还会在每个Context容器里创建多个Wrapper子容器。每个容器都有对应的访问路径,你可以通过下面这张图来帮助你理解。

假如有用户访问一个URL,比如图中的http://user.shopping.com:8080/order/buy,Tomcat如何将这个URL定位到一个Servlet呢?

首先,根据协议和端口号选定Service和Engine。

我们知道Tomcat的每个连接器都监听不同的端口,比如Tomcat默认的HTTP连接器监听8080端口、默认的AJP连接器监听8009端口。上面例子中的URL访问的是8080端口,因此这个请求会被HTTP连接器接收,而一个连接器是属于一个Service组件的,这样Service组件就确定了。我们还知道一个Service组件里除了有多个连接器,还有一个容器组件,具体来说就是一个Engine容器,因此Service确定了也就意味着Engine也确定了。

然后,根据域名选定Host。

Service和Engine确定后,Mapper组件通过URL中的域名去查找相应的Host容器,比如例子中的URL访问的域名是user.shopping.com,因此Mapper会找到Host2这个容器。

之后,根据URL路径找到Context组件。

Host确定以后,Mapper根据URL的路径来匹配相应的Web应用的路径,比如例子中访问的是/order,因此找到了Context4这个Context容器。

最后,根据URL路径找到Wrapper(Servlet)。

Context确定后,Mapper再根据web.xml中配置的Servlet映射路径来找到具体的Wrapper和Servlet

参考

servlet的本质是什么,它是如何工作的?

特别说明:本文图片,均引自极客时间。


作者:Wuxinshui
出处:http://wuxinshui.github.io
版权归作者所有,转载请注明出处

Post Directory