21张图解析Tomcat运行原理与架构全貌

21张图解析Tomcat运行原理与架构全貌💥通宵爆肝

前言

早年间,精通CRUD的小菜同学在Tomcat上通过继承HttpServlet进行CRUD

后来,有了Spring MVC框架的DispatcherServlet,让小菜更容易的进行CRUD

到现在,Spring Boot框架内嵌Web服务器,让小菜更轻松、更便捷的专注CRUD

小菜保持专一的原则,一心只关注CRUD,从未对服务器、框架有过”非分之想“

突然有一天,小菜不知道改动了哪里,程序跑不起来了

小菜心想:程序跑不了,那我岂不是得跑了?不行,不行,大环境这么恶劣,我可不能跑啊

于是,小菜开始查看各种中间件的运行原理,抽丝剥茧一层一层解析各种各样的中间件…

架构

Tomcat作为Java实现的Web服务器,是Java Web开发最流行的Web服务器之一

本文作为解析Tomcat专栏的第一篇文章,将带你深入Tomcat的运行流程,一步步揭开Tomcat精妙设计的核心组件,探索Tomcat那不为人知的高效奥秘

先来一张经过本文探索得到的流程图(熟悉Tomcat的同学可以复习复习,不熟悉的同学看完本篇文章就能理解~)

作为Web服务器,那必须要先处理网络请求,处理完网络通信,再进行业务处理

在这个过程中,秉承着高内聚、低耦合的设计思想,可以划分为两个组件处理这些事情

  1. Connector(连接器):负责处理网络通信
  2. Container(容器):负责处理业务 比如servlet容器

连接器

连接器处理网络通信又可以分为多个步骤:处理通信(获取socket)、解析协议、封装请求/响应

在Tomcat中这三个工作分别交给三个组件进行处理:

  1. EndPoint:处理通信、获取socket
  2. Processor:解析本次网络请求的协议
  3. Adapter:封装解析的请求/响应交给容器

AbstractEndPoint

EndPoint从名称上看就知道是做点到点的通信,传输层与应用层间使用Socket处理网络通信

Tomcat 9中实际没有EndPoint的接口,只有抽象类,具体实现只有两种:

  1. NioEndPoint:基于多路复用模型的NIO
  2. Nio2EndPoint:基于AIO的NIO2

EndPoint能够使用不同的IO模型来实现网络通信获取Socket

不太理解IO模型的同学可以看看这篇文章喔~

EndPoint实际上还有一种APR的实现(AprEndpoint):在早期JDK NIO性能并不理想,使用编写的本地库来提升性能,后来在Tomcat 10被舍弃

Processor

Processor组件的接口是Processor 用于解析协议

从AbstractProcessor的实现类中可以看到,它可以解析HTTP、AJP协议(AJP协议更高效,比如Nginx反向代理使用AJP会更快)

UpgradeProcessorBase则是用于协议升级,比如实现WebSocket

Processor能够解析协议,将流解析为Tomcat中封装的请求与响应

ProtocolHandler

Tomcat在设计上将动态变化的EndPoint、Processor组合成ProtocolHandler:负责网络通信获取Socket并将流解析为请求/响应

EndPoint可以使用NIO、NIO2的方式进行网络通信,而Processor能够解析HTTP、AJP协议

在ProtocolHandler中设计上却又使用了继承的方式,当动态变化的值太多时,会导致继承类爆炸(好在这里只有 2*2=4)

ProtocolHandler只是将两个能够动态改变的子组件进行组合

Adapter

Adapter 从名称就知道它是适配器模式

Processor解析流封装的请求/响应是Tomcat中定义的,Adapter将请求/响应转化为Servlet的请求/响应,方便后续容器进行处理

Adapter适配器转换请求/响应是固定的,不会随着IO模型、协议改变,只有一个实现类

线程池

在多路复用IO模型中,当线程监听到某个通道上数据就绪(发生事件),就可以进行处理

由于可能多个通道同时发生事件,此时肯定不能让监听的线程同步进行处理的,否则会阻塞后续的流程

因此会使用线程池对工作线程进行管理,监听到通道上数据就绪后,就交给工作线程执行后续任务

实际上EndPoint不仅存在线程池还涉及其他组件

这里的线程池是Tomcat自己实现的,并不是JUC下实现的线程池

思考:为什么Tomcat总是自己实现组件呢?为什么不使用已有的轮子呢?网络通信也是自己实现,为啥不用Netty呢?

(这一系列问题以及其他组件,后续单独专注于组件的文章再进行讨论)

多连接器

连接器中不变的是Adapter适配器,变动的是IO模型、协议、端口等

那么Tomcat是否支持多个不同的连接器由一个容器处理呢?

答案是支持的,Tomcat为了方便扩展设计成支持多个不同的连接器绑定同一个容器(Spring Boot中用默认HTTP、NIO、8080的连接器)

默认连接器使用Http11NioProtocol监听8080端口(HTTP、NIO、8080)

在默认的基础上增加一个连接器,使用AjpNio2Protocol监听6666(AJP、NIO2、6666)

运行时会根据端口、协议找到连接器进行处理

(文章就不贴Spring Boot扩展的代码了😁感兴趣的同学可以直接去末尾Git仓库查看)

2024-04-24 17:22:32.474  INFO 25672 --- [main]
o.s.b.w.embedded.tomcat.TomcatWebServer  : 
Tomcat initialized with port(s): 8080 (http) 6666 (ajp)

从日志上可以看到Tomcat监听端口变多了

容器

如果让我们来设计容器,很多人的第一反映肯定就是设计一个Servlet容器

当连接器处理完通信,封装好请求,直接交给这个Servlet容器进行处理

但是Tomcat并没有只单独设计一个Servlet容器

为了能够灵活扩展,Tomcat设计多层父子容器:Engine、Host、Context、Wrapper

  1. Wrapper代表Servlet,为最底层的容器,真正处理业务,不能再有子容器
  2. Context代表Web应用,能够包含多个Wrapper,即一个Web应用可以包含多个Servlet
  3. Host代表域名,即虚拟站点,每个Host允许有多个Context
  4. Engine代表引擎,最顶层容器,有且只有一个,允许有多个Host

这些容器接口都实现Container容器接口,其中都有对应的标准实现StandardXX,标准实现一般都继承抽象父类ContainerBase

一般只在标准实现上进行扩展,比如Spring Boot内嵌Tomcat:TomcatEmbeddedContext继承StrandardContext

为了方便理解,举个HTTP请求的案例:

http://cart.caicaijava.com:8080/caicai/add

首先请求会经过连接器进行处理,连接器处理完将请求交给顶级容器Engine

假设配置两

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值