阅读 108

深入拆解Tomcat & Jetty-学习笔记(2)模块二之tomcat系统架构

整体架构

概述

我们知道如果要设计一个系统,首先是要了解需求。 Tomcat 要实现 2 个核心功能:

  1. 处理 Socket 连接,负责网络字节流与 Request 和 Response 对象的转化。
  2. 加载和管理 Servlet,以及具体处理 Request 请求。 因此 Tomcat 设计了两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情。连接器负责对外交流,容器负责内部处理。

从图上你可以看到,最顶层是 Server,这里的 Server 指的就是一个 Tomcat 实例。一个 Server 中有一个或者多个 Service,一个 Service 中有多个连接器和一个容器。连接器与容器之间通过标准的 ServletRequest 和 ServletResponse 通信。

连接器的设计

什么是连接器

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

连接器的功能细化

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

连接器的子模块

通过分析连接器的详细功能列表,我们发现连接器需要完成 3 个高内聚的功能:

  • 网络通信。
  • 应用层协议解析。
  • Tomcat Request/Response 与 ServletRequest/ServletResponse 的转化。 这个三个功能对应了组件来完成其功能:
  • endpoint :是通信端点,对传输层的抽象,它可以有不同的I/O模型的实现 如非阻塞 I/O、异步 I/O 或者 APR
  • processor :Processor 用来实现 HTTP 协议,Processor 接收来自 Endpoint 的 Socket,读取字节流解析成 Tomcat Request 和 Response 对象,并通过 Adapter 将其提交到容器处理,Processor 是对应用层协议的抽象。这一层可以有Http协议的变化,如 HTTP、HTTPS、AJP
  • adapter

ProtocolHandler 组件

ProtocolHandler 组件是将EndPoint和Processor两个组件进行了封装 下图为两个维度不同的组合

连接器组件图

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

Adapter 组件

Adapter 组件完成 Tomcat Request ->Servlet Request的转换 CoyoteAdapter 负责将 Tomcat Request 转成 ServletRequest,再调用容器的 service 方法。

为什么要多一层adapter?

问题:在processor直接转换为容器的servletrequest和servletresponse不是更好,为什么要先转化Tomcat的request和response,再用adapter做一层转换消耗性能?

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

tomcat和nginx的性能差异出现的原因?
  1. Nginx/Apche一般做反向代理和处理静态HTML资源,做的事情相对来说简单,KPI就是要快,因此用C语言实现,直接调用操作系统API,充分利用操作系统的高级特性。

  2. 而Tomcat用来处理动态请求,还需要跑Java应用,因此用Java实现,因此”快“不是它主要的KPI。Java调用操作系统API要通过JNI,无形中有性能损耗。另外Tomcat通过使用Apache APR本地库来做I/O通信,性能已经跟Apache、Nginx接近了。

同一个tomcat可以设置多个端口号来启动多个应用吗?

可以的,在server.xml配置多个service,或者同一个service里配置多个connector

多层容器的设计

容器的层次结构

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

一个 Tomcat 实例包含一个service 一个 Service 包含一个 Engine和多个连接器, 一个 Engine 包含多个 Host, 一个 Host包 含多个Context 一个 Context包含多个 Wrapper 一个 Wrapper包含一个servlet

请求定位 Servlet 的过程

  • 由 Mapper 组件完成, Mapper 组件保存了容器组件与访问路径的映射关系, 根据请求的 URL 进行定位

    • 端口号定位出 Service 和 Engine
    • 域名定位出 Host
    • URL 路径定位出 Context Web 应用
    • URL 路径定位出 Wrapper ( Servlet )
  • 在各个层次定位过程中, 都会对请求做一些处理

    • 通过 Pipeline-Valve 实现容器间的互相调用 ( 责任链模式 )
    • valve 表示一个处理点 ( 如权限认证, 日志等), 处理请求; valve 通过链表串联, 并由 pipeline 维护
    • valve 会通过 getNext().invoke() 调用下一个 valve, 最后一个 valve ( Basic ) 调用下一层容器的 pipeline 的第一个 valve
    • Adapter 调用 Engine pipeline 的第一个 valve
    • Wrapper 最后一个 valve 会创建一个 Filter 链, 并最终调用 Servlet 的 service 方法

  • valve 与 filter 对比
    • valve 是 Tomcat 的私有机制, Filter 是 Servlet API 公有标准
    • valve 工作在容器级别, 拦截所有应用; Servlet Filter 工作在应用级别, 只能拦截某个应用的请求
Tomcat 内的 Context 组件跟 Servlet 规范中的 ServletContext 接口有什么区别?跟 Spring 中的 ApplicationContext 又有什么关系?
  • Servlet规范中ServletContext表示web应用的上下文环境,而web应用对应tomcat的概念是Context,所以从设计上,ServletContext自然会成为tomcat的Context具体实现的一个成员变量
  • tomcat内部实现也是这样完成的,ServletContext对应tomcat实现是org.apache.catalina.core.ApplicationContext,Context容器对应tomcat实现是org.apache.catalina.core.StandardContext。ApplicationContext是StandardContext的一个成员变量。
  • Spring的ApplicationContext之前已经介绍过,tomcat启动过程中ContextLoaderListener会监听到容器初始化事件,它的contextInitialized方法中,Spring会初始化全局的Spring根容器ApplicationContext,初始化完毕后,Spring将其存储到ServletContext中。总而言之,Servlet规范中ServletContext是tomcat的Context实现的一个成员变量,而Spring的ApplicationContext是Servlet规范中ServletContext的一个属性。
Basic valve 有些疑惑。比如engine容器下有多个host容器,那engine容器的basic valve是怎么知道要指向哪个host容器的value呢?

Mapper组件在映射请求的时候,会在Request对象中存储相应的Host、Context等对象,这些选定的容器用来处理这个特定的请求,因此Engine中的Valve是从Request对象拿到Host容器的。

生产环境中, 配的都是一个 tomcat 对应一个应用, 多个应用就用多个tomcat ,它和一个 tomcat 加载多个应用有什么区别么?

在同一个Tomcat实例里部署多个Web应用是为了节省内存等资源,不过配置部署有点复杂,应用之间互相影响,加上现在硬件成本将低,多应用部署比较少见了。

备注

本文是我个人学习了李号双老师的专栏课程之后,结合专栏文章和老师对同学答疑整理的学习笔记。仅供分享。更多精彩内容,大家可以扫描下方二位码,在极客时间订阅李号双老师的《深入拆解Tomcat & Jetty》。获取一手学习资料。