聊聊Tomcat的架构设计
微信公众号「后端进阶」,专注后端技术分享:Java、Golang、WEB框架、分布式中间件、服务治理等等。
老司机倾囊相授,带你一路进阶,来不及解释了快上车!
Tomcat 是 Java WEB 开发接触最多的 Servlet 容器,但它不仅仅是一个 Servlet 容器,它还是一个 WEB 应用服务器,在微服务架构体系下,为了降低部署成本,减少资源的开销,追求的是轻量化与稳定,而 Tomcat 是一个轻量级应用服务器,自然被很多开发人员所接受。
Tomcat 里面藏着很多值得我们每个 Java WEB 开发者学习的知识,可以这么说,当你弄懂了 Tomcat 的设计原理,Java WEB 开发对你来说已经没有什么秘密可言了。本篇文章主要是跟大家聊聊 Tomcat 的内部架构体系,让大家对 Tomcat 有个整体的认知。
前面我也说了,Tomcat 的本质其实就是一个 WEB 服务器 + 一个 Servlet 容器,那么它必然需要处理网络的连接与 Servlet 的管理,因此,Tomcat 设计了两个核心组件来实现这两个功能,分别是连接器和容器,连接器用来处理外部网络连接,容器用来处理内部 Servlet,我用一张图来表示它们的关系:
一个 Tomcat 代表一个 Server 服务器,一个 Server 服务器可以包含多个 Service 服务,Tomcat 默认的 Service 服务是 Catalina,而一个 Service 服务可以包含多个连接器,因为 Tomcat 支持多种网络协议,包括 HTTP/1.1、HTTP/2、AJP 等等,一个 Service 服务还会包括一个容器,容器外部会有一层 Engine 引擎所包裹,负责与处理连接器的请求与响应,连接器与容器之间通过 ServletRequest 和 ServletResponse 对象进行交流。
也可以从 server.xml 的配置结构可以看出 tomcat 整体的内部结构:
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="UTF-8"/>
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>
<Engine defaultHost="localhost" name="Catalina">
<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
<Context docBase="handler-api" path="/handler" reloadable="true" source="org.eclipse.jst.jee.server:handler-api"/>
</Host>
</Engine>
</Service>
</Server>
连接器(Connector)
连接器负责将各种网络协议封装起来,对外部屏蔽了网络连接与 IO 处理的细节,将处理得到的 Request 对象传递给容器处理,Tomcat 将处理请求的细节封装到 ProtocolHandler,ProtocolHandler 是一个接口类型,通过实现 ProtocolHandler 来实现各种协议的处理,如 Http11AprProtocol:
ProtocolHandler 采用组件模式的设计,将处理网络连接,字节流封装成 Request 对象,再将 Request 适配成 Servlet 处理 ServletRequest 对象这几个动作,用组件封装起来了,ProtocolHandler 包括了三个组件:Endpoint、Processor、Adapter。
Endpoint 在 ProtocolHandler 实现类的构造方法中创建,如下:
public Http11AprProtocol() {
super(new AprEndpoint());
}
Endpoint 组件用来处理底层的 Socket 网络连接,AprEndpoint 里面有个叫 SocketProcessor 的内部类,它负责为 AprEndpoint 将接收到的 Socket 请求转化成 Request 对象,SocketProcessor 实现了 Runnable 接口,它会有一个专门的线程池来处理,后面我会单独从源码的角度分析 Endpoint 组件的设计原理。
org.apache.tomcat.util.net.AprEndpoint.SocketProcessor#doRun:
// Process the request from this socket
SocketState state = getHandler().process(socketWrapper, event);
process 方法会创建一个 processor 对象,调用它的 process 方法将 Socket 字节流封装成 Request 对象,在创建 Processor 组件时,会将 Adapter 组件添加到 Processor 组件中:
org.apache.coyote.http11.AbstractHttp11Protocol#createProcessor:
protected Processor createProcessor() {
Http11Processor processor = new Http11Processor();
// set Adapter 组件
processor.setAdapter(getAdapter());
return processor;
}
而 Adapter 组件在连接器初始化时就已经创建好了:
org.apache.catalina.connector.Connector#initInternal:
// Initialize adapter
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
目前为止,Tomcat 只有一个 Adapter 实现类,就是 CoyoteAdapter。Adapter 的主要作用是将 Request 对象适配成容器能够识别的 Request 对象,比如 Servlet 容器,它的只能识别 ServletRequest 对象,这时候就需要 Adapter 适配器类作一层适配。
以上连接器的各个组件,我用一张图说明它们直接的关系:
容器(Container)
在 Tomcat 中一共设计了 4 种容器,它们分别为 Engine、Host、Context、Wrapper,它们的关系如下图所示:
- Engine:表示一个虚拟主机的引擎,一个 Tomcat Server 只有一个 引擎,连接器所有的请求都交给引擎处理,而引擎则会交给相应的虚拟主机去处理请求;
- Host:表示虚拟主机,一个容器可以有多个虚拟主机,每个主机都有对应的域名,在 Tomcat 中,一个 webapps 就代表一个虚拟主机,当然 webapps 可以配置多个;
- Context:表示一个应用容器,一个虚拟主机可以拥有多个应用,webapps 中每个目录都代表一个 Context,每个应用可以配置多个 Servlet。
从上图可看出,各个容器组件之间的关系是由大到小,即父子关系,它们之间关系形成一个树状的结构,它们的实现类都实现了 Container 接口,它有如下方法来控制容器组件之间的关系:
public interface Container extends Lifecycle {
Container getParent();
void setParent(Container container);
void addChild(Container child);
Container findChild(String name);
Container[] findChildren();
void removeChild(Container child);
}
容器组件之间通过以上几个方法,即可实现它们之间的父子关系,有没有发现,Container 接口还继承了 Lifecycle 接口,它有如下方法:
public interface Lifecycle {
public static final String INIT_EVENT = "init";
public static final String START_EVENT = "start";
public static final String BEFORE_START_EVENT = "before_start";
public static final String AFTER_START_EVENT = "after_start";
public static final String STOP_EVENT = "stop";
public static final String BEFORE_STOP_EVENT = "before_stop";
public static final String AFTER_STOP_EVENT = "after_stop";
public static final String DESTROY_EVENT = "destroy";
public void addLifecycleListener(LifecycleListener listener);
public LifecycleListener[] findLifecycleListeners();
public void removeLifecycleListener(LifecycleListener listener);
public void start() throws LifecycleException;
public void stop() throws LifecycleException;
}
Tomcat 中有很多组件,组件通过实现 Lifecycle 接口,Tomcat 通过事件机制来实现对这些组件生命周期的管理。
Tomcat 的这种容器设计思想,其实是运用了组合设计模式的思想,组合设计模式最大的优点是可以自由添加节点,这样也就使得 Tomcat 的容器组件非常地容易进行扩展,符合设计模式中的开闭原则。
现在我们知道了 Tomcat 的容器组件的组合方式,那我们现在就来想一个问题:
当一个请求过来时,Tomcat 是如何识别请求并将它交给特定 Servlet 来处理呢?
从容器的组合关系可以看出,它们调用顺序必定是:
Engine -> Host -> Context -> Wrapper -> Servlet
那么 Tomcat 是如何来定位 Servlet 的呢?答案是利用 Mapper 组件来完成定位的工作。
Mapper 最主要的核心功能是保存容器组件之间访问路径的映射关系,它是如何做到这点的呢?
我们不妨先从源码入手:
org.apache.catalina.core.StandardService:
protected final Mapper mapper = new Mapper();
protected final MapperListener mapperListener = new MapperListener(this);
Service 实现类中,已经初始化了 Mapper 组件以及它的监听类 MapperListener,这里先说明一下,在 Tomcat 组件中,标准的实现组件类前缀会有 Standard,比如:
org.apache.catalina.core.StandardServer
org.apache.catalina.core.StandardService
org.apache.catalina.core.StandardEngine
org.apache.catalina.core.StandardHost
org.apache.catalina.core.StandardContext
org.apache.catalina.core.StandardWrapperz
在 Service 服务启动的时候,会调用 MapperListener.start() 方法,最终会执行 MapperListener 的 startInternal 方法:
org.apache.catalina.mapper.MapperListener#startInternal:
Container[] conHosts = engine.findChildren();
for (Container conHost : conHosts) {
Host host = (Host) conHost;
if (!LifecycleState.NEW.equals(host.getState())) {
// Registering the host will register the context and wrappers
registerHost(host);
}
}
该方法会注册新的虚拟主机,接着 registerHost() 方法会注册 context,以此类推,从而将容器组件直接的访问的路径都注册到 Mapper 中。
定位 Servlet 的流程图:

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
【图解AI】什么是语义分割、实例分割、全景分割
图像分割(image segmentation)是计算机视觉中非常重要的研究和应用方向,是根据某些规则将图片中的像素分成不同的部分、打上不同标签。图解如下: 1、图像分类(image classification) 识别图像中存在的内容,如下图,有人(person)、树(tree)、草地(grass)、天空(sky) 2、目标检测(object detection) 识别图像中存在的内容和检测其位置,如下图,以识别和检测人(person)为例 3、语义分割(semantic segmentation) 对图像中的每个像素打上类别标签,如下图,把图像分为人(红色)、树木(深绿)、草地(浅绿)、天空(蓝色)标签 4、实例分割(instance segmentation) 目标检测和语义分割的结合,在图像中将目标检测出来(目标检测),然后对每个像素打上标签(语义分割)。对比上图、下图,如以人(person)为目标,语义分割不区分属于相同类别的不同实例(所有人都标为红色),实例分割区分同类的不同实例(使用不同颜色区分不同的人) 5、全景分割(panoptic segmentation...
-
下一篇
docker CE on Linux示例浅析(五)服务编排
概述 github项目地址:https://github.com/superwujc 尊重原创,欢迎转载,注明出处:https://my.oschina.net/superwjc/blog/3056296 历史系列: docker CE on Linux示例浅析(一)安装与基本运行 docker CE on Linux示例浅析(二)数据存储与持久化 docker CE on Linux示例浅析(三)镜像与容器管理 docker CE on Linux示例浅析(四)swam群集配置 分布式应用程序的部署需要处理每一个逻辑层以及各层之间的关系,如前端代理,web应用,消息队列,缓存,数据库等。容器化部署为此引入服务编排(orchestration)的概念,用于集中控制容器的生存周期与运行参数,包括但不限于以下方面: 容器部署 资源控制 负载均衡 健康检查 应用配置 规模伸缩 位置迁移 docker-ce原生提供了compose与stack两种方式,通过定义在配置文件中的容器运行参数对服务进行编排,配置文件的格式可以为yaml或json。本文以前端代理(nginx) + web程序(to...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- Docker安装Oracle12C,快速搭建Oracle学习环境
- 面试大杂烩
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS关闭SELinux安全模块
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2全家桶,快速入门学习开发网站教程