Tomcat的架构设计(生命周期管理、可扩展的容器组件设计、类加载方式)可以为服务器中间件的设计、系统组件设计提供非常好的借鉴意义

总体设计

Server

  • 最基本的功能
    接收其他计算机(客户端)发来的请求数据并进行解析,完成相关业务处理,然后把处理结果作为响应返回给请求计算机(客户端)
    image.png
  • 实现
    Socket监听服务器指定端口,调用start方法启动服务器,打开Socket链接,监听端口,接收请求处理并返回响应,提供stop方法停止服务器释放网络资源
  • 缺点:
    扩展性差,无法适配请求处理相同但网络协议不同

Connector和Container

Tomcat诞生起就支持AJP(Apache JServ Protocol,定向包协议)和HTTP协议。

  • Web应用通过Tomcat独立部署时,选择使用HTTP协议
  • 通过Apache进行集群部署时,使用AJP协议与Web服务器(Apache)进行链接
    当应用服务器在两种部署架构下切换时,应确保Web应用不需做任何变更
    image.png
  • 实现
    一个Server可以包含多个Connector(链接器)和Container(容器)
    Connector负责开启Socket并监听客户端请求、返回响应数据;
    Container负责具体的请求处理;
    各自拥有start和stop方法加载释放自己维护的资源
  • 缺点
    Connector和Container的对应映射关系较复杂
  • 改进
    image.png
    一个Server包含多个Service(相互独立,共享一个JVM以及系统类库);
    一个Service负责维护多个Connector和一个Container,Connector只能由其所属的Service维护的Continer处理

Container设计

在Tomcat中,Container是一个更加通用的概念,与Tomcat保持一致,将Container更名为Engine,表示整个Servlet引擎(非Servlet容器,Server表示整个Servlet容器)
应用服务器是用来部署并运行Web应用的,是一个运行环境,而不是独立的业务处理系统
需要在Engine容器中支持管理Web应用,当接收到Connector的处理请求时,Engine容器能够找到一个合适的Web应用来处理
image.png

  • 实现
    使用Context表示一个Web应用,一个Engine可以包含多个Context
  • 缺点
    如果一台服务器承担多个域名的服务,如果希望运行一个服务器实例时无法办到(可以在该主机运行多个服务器实例)
  • 改进
    image.png
    服务器将每个域名抽象视为一个虚拟的主机,在每个虚拟主机下包含多个Web应用
    每个Host包含多个Context
  • 在一个Web应用中,可以包含多个Servlet实例以处理来自不同链接的请求,在Tomcat中,Servlet被定义为Wrapper
    image.png
    我们使用Container来表示容器,Container可以添加并维护子容器,Engine、Host、Context、Wrapper均继承自Container,其4者相互之间为弱依赖的关系。Service持有的是Engine接口
    image.png
    Tomcat的Container可以进行后台处理,很多情况下Container需要执行一些异步处理,而且是定期的,例如每隔30s对Web文件变更的扫描
  • Container的background方法
    其基础抽象类(ContainerBase)确保在启动组件的同时异步起动后台处理,各个容器组件实现background方法即可,无须创建异步线程

Lifecycle

  • 从抽象和复用层面来看
    所有组件均存在:启动、停止等生命周期方法;
  • 可基于生命周期进行一次接口抽象,定义生命周期的核心方法:
    Init():初始化组件
    start():启动组件
    stop():停止组件
    destory():销毁组件
    image.png
  • 采取一致的机制来初始化、启动、停止以及销毁各个组件
    Tomcat核心组件默认实现继承自LifecycleMBeanBase抽象类,其将自身组册为MBean,通过Tomcat管理工具进行动态维护
    image.png
  • 每个生命周期对应数个状态转换,很大一部分都是在生命周期方法中自动(auto)转换,不需要额外方法调用
  • 不是每个状态都会触发生命周期事件
  • 不是所有生命周期事件均存在对应状态
  • Tomcat默认提供3个与状态无关的事件类型
    1.周期事件
    2.配置启动
    3.配置停止

Pipeline和Value

增强提高组件的灵活性,使其同样易于扩展
Tomcat采用指责链模式增强组件灵活性,实现客户端请求的处理。每个Container组件通过执行一个指责链来完成具体的请求处理

  • Tomcat定义了Pipeline(管道)和Valve(阀)两个接口
    Pipeline:构造职责链
    Valve:职责链上的处理器
    image.png
  • Pipeline中维护了一个基础的Valve,始终位于Pipeline的末端最后执行,封装具体请求处理和输出响应的过程
  • addValve为其他方法添加Valve,按照添加顺序执行,Pipeline获得首个Valve来启动整个链条的执行

每个层级的Container(Engine、Host、Context、Wrapper)均有针对的基础Valve实现,同时维护了一个Pipeline实例
image.png

Connector设计

Connector需要有一下功能:

  • 监听服务器端口,读取客户端请求
  • 将请求按照指定协议进行解析
  • 根据请求地址匹配正确的容器进行处理
  • 将响应返回给客户端
    image.png
    Tomcat设计方案如上图
  • ProtocolHandler:协议处理器,针对不同协议和I/O方式,提供不同实现
  • AbstracEndpoint:启动线程监听服务器端口,接收到请求后调用Processor进行数据读取
  • Processor:读取客户端请求后,需要按照请求地址映射到具体的容器进行处理(请求映射)
    Tomcat通过Mapper维护容器映射信息,按照Servlet规范定义查找容器
    MapperLiistener实现ContainerListener和LifecycleListener在勇气组件状态发生变更时,注册或者取消对应的容器映射信息
    Tomcat通过适配器模式实现Connector和Mapper、Container的解耦
    image.png

Executor

并发,不可能让服务器执行的所有请求以串行的方式
Tomcat提供了Executor接口来表示一个可以在组件间共享的线程池(默认使用JDK5线程池技术),继承自Lifecycle
Executor由Service维护,同一个Service中的组件可以共享一个线程池
Endpoint启动一组线程监听Socket端口,接收到请求后创建请求处理对象,交由线程池处理
image.png

Bootstrap和Catalina

提供一套配置环境来支持系统的可配置性

  • Catalina
    Tomcat通过类Catalina提供了一个Shell程序:解析server.xml创建各个组件,和启动、停止应用服务器(顶层组件Server)
  • Digester
    解析XML文件,包括server.xml及web.xml
  • Bootstrap
    应用服务器入口,负责创建Catalina实例,根据执行参数调用Catalina相关方法完成针对应用服务器的操作(启动、停止)
    Bootstrap与Tomcat应用服务器完全松耦合,实现了启动入口与核心环境的解耦
    image.png
    Tomcat完整架构图如上,Server及其子组件代表了应用服务器本身,可以不通过Bootstrap和Catalina来启动服务器
    Tomcat提供了一个同名类org.apache.catalina.startuo.Tomcat,可以将Tomcat服务器嵌入到应用系统中启动,即嵌入式Web容器

TOmcat启动

image.png
Tomcat的启动过程非常标准化,统一按照生命周期管理接口Lifecycle的定义进行启动。调用init方法进行组件的逐级初始化,然后再调用start方法进行启动,每次调用均伴随着生命周期状态变更事件的触发。
每一层级组件除完成自身的处理外,还要负责调用子组件相应的周期管理方法,组件与组件之间是松耦合设计,可通过配置进行修改和替换

请求处理

Tomcat简要的处理过程示意图如下
image.png
本质上,应用服务器的请求处理开始于监听的Socket端口接收到数据,结束于将服务器处理结果写入Socket流输出
服务器将请求按照既定协议进行读取,封装为与具体通信方案无关的请求对象,根据映射规则定位到具体处理单元进行处理。业务处理结束后,结果被写入一个与通信方案无关的相应对象,按照既定协议写入输出流

类加载器

J2SE标准类加载器

三种类加载机制和双亲委派模型不做赘述,查看《深入理解JVM》
http://jtao.work/archives/xu-ni-ji-lei-jia-zai-ji-zhi
除了支持类加载器按照层级创建之外,JVM提供一套机制允许替换JCP之外生成的API,通过这个机制,可以提供新版本的API来覆盖JVM默认实现

  • 打破类加载原因
    随着版本的根据,J2SE包含越来越多的扩展,由JVM加载,甚至作为核心类库由Bootstrap类加载器加载,即便应用程序提供了新版本的包,由于重名冲突也不会被使用,必须通过Endorsed Standsands机制解决该问题。很多应用服务器都提供了该机制来提供新版本的Jar包,例如JBOss

Tomcat加载器

自行创建类加载器以实现更加灵活的控制,是Servlet规范要求每个Web都有一个独立的类加载器实例,也是架构层面的考虑

  • 隔离性
    Web应用类库相互隔离,避免依赖库或者应用包相互影响
  • 灵活性
    Web应用之间的加载器相互独立,不同Web应用不会相互影响,不必重新加载
  • 性能
    每个Web应用都有一个类加载器,在加载的时候不会搜索其他Web应用类的Jar包,性能较高
    image.png
    Tomcat类加载如上图所示,提供了3个基础的类加载器(Common、Catalina、Shared可通过catalina.properties配置)和Web应用类加载器。
  • Common:System的子加载器
    Tomcat应用服务器顶层的公共类加载器,默认指向$CATALINA_HOME/lib下的包,加载应用服务器
  • Catalina:Commnon子加载器
    加载Tomcat应用服务器的类加载器
  • Shared:Common子加载器
    作为Web应用的父类加载器
  • Web应用:Shared子加载器
    加载/WEB-INF/lib目录下的Jar包,该类加载器只对当前Web应用可见

总结

  • 共享:现了Jar包,且不会引入过多无用的包
    1.Commnon类加载器实现了Jar包在应用服务器以及Web应用之间共享
    2.Shared类加载器实现Jar包在Web应用之间的共享
    3.Catalina类加载器加载服务器依赖的类
  • 隔离性:此处指服务器与Web应用的隔离(不是Web之间的隔离)
    Catalina类加载器加载服务器依赖的包,Web应用不依赖服务器的任何实现类,便于Web应用的可移植性

Web应用类加载器

默认JVM类加载机器:双亲委派

1.从缓存中加载
2.从父类加载器中加载
3.如果父类没有,从当前类加载
4.如果都没有,抛异常

Tomcat的Web应用类加载器

特指Web应用类加载器
除JVM基础类库外,首先尝试通过当前类加载器加载,然后才进行委派,Servlet规范禁止Web应用类加载器加载相关的Servlet的API,所以Web应用中不要包含相关包
1.从缓存中加载
2.从JVM的Bootstrap类加载器加载
3.从当前类加载器加载(按照/WEB-INF/classes\WEB-INF/lib的顺序)
4.从父类加载器加载,采用双亲委派,顺序为System、Common、Shared

  • Tomcat控制是否启动双亲委派
    提供delegate属性控制,默认为false,当配置为true时使用Java默认的委派模式

  • 其他
    Tomcat还可通过packageTriggersDeny属性只让某些包路径采用Java的委派模式


这个家伙很懒,啥也没有留下😋