Tomcat总体架构演变

本篇博文是根据《Tomcat架构解析》一书为基础,以一种启发式的讲解方式来介绍Tomcat的总体架构

  • 从最基本 的功能来讲,我们可以将服务器描述成这样一个应用:它接受其他计算机(客户端)发来的请求数据并进行解析,完成相关业务处理,然后把处理结果作为响应返回给请求计算机(客户端)。下图是一个最简单的服务器设计图:我们通过start()方法启动服务器,打开socket链接,监听服务器端口,并负责在接受到客户端请求时进行处理并返回响应。同事提供一个stop()方法来停止服务器并释放网络资源。    

  • 很快我们就会发现,将请求监听与请求处理放在一起扩展性很差,比如当我们想适配多种网络协议,但是请求处理相同的时候。于是,我们进行了如下改进

一个server可以包含多个connector 和 container。其中前者负责开启socket并监听客户端请求、返回响应数据;后者负责具体的响应请求。他们分别拥有自己的start()和stop()方法来加载和释放自己维护的资源。

  • 上述的设计有一个很明显的缺陷。既然server可以包含多个connect和container,那么如何知晓来自某个connecor的请求由哪个container处理。我们可以维护一个复杂的关系表,但这不是必须的。改进如下图:

一个server可以包含多个service(他们互相独立,只是共享一个JVM以及系统类库),一个service负责维护多个connector和一个container,这样来自connector的请求只能由它所属serveice维护的container处理。

而container是一个通用概念,所以我们将其重新命名为engine,用以表示整个servlet引擎。这里的engine表示整个servlet引擎而非servlet容器,他只负责请求处理,并不需要考虑请求链接、协议等的处理。server才表示整个servlet容器。


上文我们在打的方面进行了解耦,而下文我们将从两部分分开深入解耦。


container设计

  • 应用服务器是用来部署并运行web应用的,是一个运行环境,而不是一个独立的义务处理系统。因此我们需要在engine容器中支持管理web应用,当接收到connector的处理请求时,engine容器能够找到一个合适的web应用来处理。我们用context来表示一个web应用,并且一个engine可以包含多个context(同样具备start和stop方法, 用以在启动时加载资源以及在停止时释放资源)。

  • 设想我们有一台主机,他承担了多个域名的服务,因此我们要提供多个域名的服务,那么就可以将每一个域名视为一个虚拟主机,在每个虚拟主机下包含多个web应用。

  • 在一个web应用中,可包含多个servlet实例以处理来自不同连接的请求,我们用wrapper的概念来表示这个组件。

  • engine 、host、context、wrapper 都属于组件,尽管具体操作可能委派到不同的组件完成,但是他们的行为是一致的。因为我们可以抽象一个大的容器来维护这些子组件。

  • 正如上一个container所说,我们提供了container来对子组件进行了管理,那么我们同样可以提供通用性定义用于应用服务器的统一管理。我们很容易发现,所有组件都拥有启动、停止等生命周期方法,拥有生命周期管理的特性。因此,我们可以基于生命周期进行一次接口抽象。

  • 至此的应用服务器设计主要完成了我们对核心概念的分解,确保了整体架构的可伸缩性和可扩展性,可除此之外,我们还要考虑每个组件的灵活性,使其易于扩展。

在增强组件灵活性和可扩展性方面,责任链模式是一种比较好的选择。Tomcat定义了Pipeline(管道)和valve(阀)两个接口。前者用于构造责任链,后者代表责任链上的每个处理器。我们可以这样理解:来自客户端的请求就像流经管道的水一般,经过每个阀进行处理。

Tomcat容器组件的灵活之处在于,每个层次的容器(engine、host、context、wrapper)均有对应的基础valve实现,同时维护了一个Pipeline。也就是说我们可以在任何层级的容器上针对请求处理进行扩展。

connector设计

要想与container配合实现一个完整的服务器功能,connector至少要完成如下几项功能:

监听服务器端口,读取来自客户端的请求。

将请求数据按照指定协议进行解析。

根据请求地址匹配正确的容器进行处理。

将相应返回客户端。


  • protocolHandler表示一个协议处理器,针对不同的协议和I/O方式,提供了不同的实现。其中endpoint用于启动socket监听,按照I/O方式进行分类实现。还包含一个processor用于按照指定协议读取数据,并将请求交由容器处理
  • 当processor读取客户端请求后,需要按照地址映射到具体容器进行处理,同时需要考虑容器组件的注册与销毁。
  • Mapper用于维护容器映射信息,同时按照映射规则查找容器。
  • MapperListener 实现了containerListener和 lifecycleListener,用于在容器组件状态变更时,注册获取取消对应的容器映射信息。再service启动时,会自动作为监听器注册到各个容器组件上,同时将已创建的容器注册到Mapper。
  • Tomcat通过适配器模式实现了connector与Mapper、container的解耦。Tomcat默认的connector实现了对应的适配器coyoteAdapter。

接下来我们考虑应如何设计应用服务器的并发方案。

根据之前的设计方案,我们同样希望线程池作为一个组件进行统一管理。因此,Tomcat提供了Executor来表示一个可以再组件间共享的线程池。


Tomcat中的executor由service维护,因此同一个service中的组件可以共享一个线程池。

最后。

Tomcat通过类Catalina提供了一个shell程序,用于解析server.xml创建各个组件,同时启动、停止应用服务器。

Tomcat提供了bootstrap作为应用服务器启动的入口,他负责创建Catalina实例,根据参数调用catalina相关方法完成针对应用服务器的操作。

bootstrap与Tomcat应用服务器完全松耦合,他可以直接依赖JRE运行并为Tomcat应用服务器穿件共享类加载器,用于构造Catalina实例以及整个Tomcat服务器。


猜你喜欢

转载自blog.csdn.net/qq_38500786/article/details/80656828
今日推荐