掘金 后端 ( ) • 2024-04-25 09:37

在了解Tomcat各个核心组件的时候强烈建议大家读下源码,之前也看过好多次tomcat的文章,但是基本都是看的时候明白,看过就忘记了。如果伴着源码去读下各个组件能更好的理解并加深记忆,光看博文不实际去动手试试是难以真正理解的。“纸上得来终觉浅,绝知此事要躬行”学习任何东西都是这个道理。

tomcat核心组件

Tomcat整体的组件架构如上图所示,这个跟server.xml中的配置是保持一致的。顶级组件为Server跟Service,一个Tomcat中只有一个Server,Server下可以启动多个Service。Service下最主要的两大组件Connector跟Container,Connector负责接收请求包装成request、response,而Container负责处理请求返回response。下面我们来从上到下介绍下每个组件。

Server

Server代表完整的Tomcat实例在Java虚拟集中是单例,主要是用来管理容器下各个Serivce组件的生命周期。下图描述了Server组件的关键方面。如图所示,Server实例是通过server.xml配置文件来配置的;其根元素所代表的正是Tomcat实例,默认实现为org.apache.catalina.core.StandardServer。但是,你也可以通过标签的class属性来自定义服务器实现。

server

服务器重要的一方面就是它打开了8005端口(默认端口)来监听关闭服务命令(默认情况下命令为SHUTDOWN)。当收到shutdown命令后,服务器会优雅的关闭自己。同时出于安全考虑,发起关闭请求的连接必须来自同一台机器上的同一个运行中的Tomcat实例。

此外,Server还提供了一个Java命名服务和JNDI服务,可以通过这两个服务可以使用名称来注册专用对象(如数据源配置)。在运行期,单个组件(如Servlet)可以使用对象名称来通过服务器的JNDI绑定服务来查找需要的对象相关信息。

Service

Service将一组Connector组件和Engine关联起来,代表Tomcat中一组请求处理的组件。Service仅仅是一个分组结构,它并不包含任何其他的附加功能。

service

一个Service集中了一些连接器,每个连接器监控一个指定的IP及端口并通过指定的协议做出响应。客户端请求首先到达连接器(Connector),连接器在再将这些请求轮流传入Engine中处理,Engine是Tomcat中请求处理的关键组件。上图中展示了HTTP连接器、HTTPS连接以及AJP组件。

一般很少会修改这个元素,默认的Service实例通常就足够使用了。

Connector

Connector是客户端连接到Tomcat容器的服务点,它为引擎提供协议服务来将引擎与客户端各种协议隔离开来,如HTTP、HTTPS、AJP协议。

Connector需要完成的核心工作是:1)网络通信2)应用层协议解析3)Tomcat Request/Response 与 ServletRequest/ServletResponse 的转化。为了完成这些事情Tomcat 的设计者设计了 3 个组件来实现这 3 个功能,分别是 EndPoint、Processor 和 Adapter,其中 Endpoint和 Processor放在一起抽象成了 ProtocolHandler组件。一个请求来了处理的过程如下图所示。

Connector

ProtocolHandler

Tomcat 支持的应用层协议有:

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

Tomcat支持的 I/O 模型有:

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

应用层协议是变化的,I/O模型也是变化的,但是整体的处理逻辑是不变的,EndPoint 负责提供字节流给 Processor,Processor负责提供 Tomcat Request 对象给 Adapter,Adapter负责提供 ServletRequest对象给容器。Tomcat使用模板方法设计模式,设计了一系列抽象基类来封装这些稳定的部分,抽象基类 AbstractProtocol实现了 ProtocolHandler接口。每一种应用层协议有自己的抽象基类,比如 AbstractAjpProtocol和 AbstractHttp11Protocol,具体协议的实现类扩展了协议层抽象基类。

协议类图

EndPoint

endpoint

要了解EndPoint可以先看这张图,已NioEndPoint为例,EndPoint中主要包含LimitLatch、Acceptor、Poller、SocketProcessor 和Executor 5 个组件,共同来完成对TCP/IP协议的处理。

LimitLatch是连接控制器,它负责控制最大连接数,NIO 模式下默认是 10000,达到这个阈值后,连接请求被拒绝。

Acceptor线程组,单独线程,死循环,用于接受新连接,并将新连接封装一下,选择一个Poller将SocketChannel添加到 Poller 的事件队列中。

Poller线程组,也是单独线程,死循环。每个Poller线程都有自己的Queue。每个Poller线程可能被多个 Acceptor线程注册PollerEvent。Poller不断的通过内部的Selector对象向内核查询Channel的状态,一旦可读就生成任务类 SocketProcessor 交给 Executor 去处理。Poller 的另一个重要任务是循环遍历检查自己所管理的 SocketChannel是否已经超时,如果有超时就关闭这个SocketChannel。Acceptor与Poller线程之间通过Queue通信,典型的生产者消费者。

SocketProcessor主要是创建相应的Processor,并将SocketWrapperBase对象交由Processor处理。 Executor就是线程池,这个线程池维护的线程就是我们非常熟悉的“http-nio-8080-exec-N”线程,也就是用户请求的实际处理线程。主要负责运行SocketProcessor任务类,以及后续的处理。

Processor

Processor 主要是对应用层协议的抽象。Processor 接收来自 EndPoint 的 SocketWrapper,读取字节流做解析,例如Http11Processor会按照HTTP1.1协议进行数据解析,构建 Tomcat Request 和 Response 对象,并通过 Adapter 将其提交到容器处理。

Adapter

由于协议的不同,Tomcat 定义了自己的 Request 类来存放请求信息,这里其实体现了面向对象的思维。但是这个 Request 不是标准的 ServletRequest ,所以不能直接使用 Tomcat 定义 Request 作为参数直接容器。

Tomcat 设计者的解决方案是引入 CoyoteAdapter,这是适配器模式的经典运用,连接器调用 CoyoteAdapter 的 Sevice 方法,传入的是 Tomcat Request 对象,CoyoteAdapter负责将 Tomcat Request 转成 ServletRequest,再调用容器的 Service方法。在转换 ServletRequest 的时候会对请求链接进行解析,映射到请求指定的 Host 以及 Context 上(存放在ServletRequest中的MappingData),为了后续传递给容器提供基础。

Container

容器是用于封装和管理Servlet,以及具体处理Request请求。它由四个子容器组件构成,分别是Engine、Host(站点)、Context(应用)、Wrapper(Servlet)。这四个组件不是平行关系,而是父子包含关系。除Engine外都可存在多个。

Container

你可能会问,为啥要设计这么多层次的容器,这不是增加复杂度么?其实这背后的考虑是,Tomcat 通过一种分层的架构,使得 Servlet 容器具有很好的灵活性。因为这里正好符合一个 Host 多个 Context, 一个 Context 也包含多个 Servlet,而每个组件都需要统一生命周期管理,所以使用组合模式设计这些容器。

public interface Container extends Lifecycle {
    public void setName(String name);
    public Container getParent();
    public void setParent(Container container);
    public void addChild(Container child);
    public void removeChild(Container child);
    public Container findChild(String name);
}

所有容器组件都实现了 Container 接口,因此在使用容器组件的时候具有一致性。我们看到了getParent、SetParent、addChild和 removeChild等方法,这里就是通过组合模式来统一管理所有容器。我们还看到 Container 接口拓展了 Lifecycle ,Tomcat 就是通过 Lifecycle 统一管理所有容器组件的生命周期。

Engine

引擎表示可运行的Catalina的Servlet引擎实例。在一个服务中只能有一个引擎,同时,作为一个真正的容器,Engine元素之下可以包含一个或多个虚拟主机。

作为请求处理的主要组件,它接收Connector传入请求的对象以及输出相应结果。Engine没有复杂的功能,代码也只有400多行,它主要功能是将传入请求委托给适当的虚拟主机处理。

Engine

Host

Host 顾名思义就是虚拟主机。Host 是 Engine 的子容器,一个 Host 在 Engine 中代表一个虚拟主机,这个虚拟主机的作用就是运行应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器是 Context,它除了关联子容器外,还有就是保存一个主机应该有的信息。每个虚拟主机下都可以部署一个或者多个 Web App,每个 Web App 对应于一个 Context,当 Host 获得一个请求时,会将请求转发到某个 Context 来处理。

host

像上图所示,不同域名的请求在经过 Engine 后会交由不同的 Host 来处理(如果找不到指定的Host会交由默认的 Host 来处理)。但是现实中为了保证每个服务的稳定性、隔离性等,基本上都是一台机器只会部署一个 Host(localhost),也是默认的 Host。同时如果每个服务都配置相应的域名的话也违背了依赖倒置原则,造成扩展性比较差。

Context

Context它表示就是Web应用程序本身(我们部署在Tomcat中的应用),它具备了 Servlet 运行的基本环境。理论上只要有 Context 就能运行 Servlet 了。简单的 Tomcat 可以没有 Engine 和 Host。Context 最重要的功能就是管理它里面的 Servlet 实例,Servlet 实例在 Context 中是以 Wrapper 出现的。

context

Wrapper

Wrapper是 Context 容器的子容器,表示一个 Servlet(或者由jsp文件转换而来的servlet)。它之所以称为包装器是因为它包装了java.servlet.Servlet实例。这是容器层次结构的最底层,添加任何子类都会导致异常。

wrapper

Wrapper负责它所包装的Servlet的整个生命周期,包括加载、初始化、执行、回收,执行Servlet的方法如init()、service()、destroy()。

其他组件

Valve

注意是Valve不是Value,开始的时候还因为看错迷惑了半天。Valve是处理元素,它可以被包含在每个Tomcat容器的中,负责处理请求,并将请求传递给下层容器。但是对于容器来说,它并不会直接持有Valve的引用,而是会持有一个称作管道Pipeline 的单一实体的引用,并用这个管道来关联的一系列Valve。

public interface Valve {
  public Valve getNext();
  public void setNext(Valve valve);
  public void invoke(Request request, Response response)
}
public interface Pipeline {
  public void addValve(Valve valve);
  public Valve getBasic();
  public void setBasic(Valve valve);
  public Valve getFirst();
}

Pipeline+Valve使用的是典型的责任链模式,Pipeline中有addValve方法,维护了Valve链表。Pipeline 中没有 invoke 方法,因为整个调用链的触发是 Valve 来完成的,Valve完成自己的处理后,调用 getNext.invoke() 来触发下一个 Valve 调用。

Pipeline中还有个 getBasic方法。BasicValve处于 Valve链表的末端,它是 Pipeline中必不可少的一个 Valve,负责调用下层容器的 Pipeline 里的第一个 Valve。

请求处理

Tomcat中的请求传递过程如上图所示,Value的机制跟Filter的机制非常像,主要是有以下的区别

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

京东年薪60W架构师带你深入拆解Tomcat,Endpoint

Apache Tomcat 8 Configuration Reference

详解Tomcat系列(一)-从源码分析Tomcat的启动

Tomcat系列(4)——Tomcat 组件及架构详细部分

谈谈 Tomcat 请求处理流程