Tomcat总体架构(二)

目录

八、PipeLine 和 Valve

九、Connector

十、Executor

十一、Bootstrap 和 Catalina

十二、组件总结

N、结束

视频

八、PipeLine 和 Valve

从架构设计的角度来考虑,至此的应用服务器设计主要完成了我们对核心概念的分解,确保了整体架构的可伸缩性和可扩展性,除此之外,我们还要考虑如何提高每个组件的灵活性,使其同样易于扩展.

在增强组件的灵活性和可扩展性方面,职责链模式是一种比较好的选择.Tomcat即采用该模式来实现客户端请求的处理——请求处理也是职责链模式典型的应用场景之一.换句话说,在Tomcat中每个Container组件通过执行一个职责链来完成具体的请求处理.

Tomcat定义了Pipeline(管道)和Valve(阀)两个接口.前者用于构造职责链,后者代表职责链上的每个处理器.

当然,我们还可以从字面意思来理解这两个接口所扮演的角色——来自客户端的请求就像是流经管道的水一般,经过每个阀进行处理. 其设计如图2-11所示.

Tomcat总体架构(二)

Pipeline中维护了一个基础的Valve,它始终位于Pipeline的末端(即最后执行),封装了具体的请求处理和输出响应的过程.然后,通过addvalve()方法,我们可以为Pipeline添加其他的Valve.后添加的Valve位于基础Valve之前,并按照添加顺序执行.Pipeline通过获得首个Valve来启动整个链条的执行.

Tomcat容器组件的灵活之处在于,每个层级的容器(Engine、Host、Context、 Wrapper)均有对应的基础Valve实现,同时维护了一个Pipeline实例.也就是说,我们可以在任何层级的容器上针对请求处理进行扩展.

由于Tomcat每个层级的容器均通过Pipeline和Valve进行请求处理,那么,我们很容易将一些通用的Valve实现根据需要添加到任何层级的容器上. 修改后的应用服务器设计如图2-12所示.

Tomcat总体架构(二)

九、Connector

前面我们重点讨论了容器组件的设计,集中于如何设计才能确保容器的灵活性和可扩展性,并做到合理的解耦.

接下来,我们再细化一下服务器设计中的另一个重要组件——Connector.

要想与Container配合实现一个完整的服务器功能,Connector至少要完成如下几项功能.

  • 监听服务器端口,读取来自客户端的请求.

  • 将请求数据按照指定协议进行解析.

  • 根据请求地址匹配正确地容器进行处理.

  • 将响应返回客户端.

只有这样才能保证将接收到的客户端请求交由与请求地址匹配的容器处理.

我们知道,Tomcat支持多协议,默认支持HTTP和AJP.同时,Tomcat还支持多种IO方式, 包括BIO ( 8.5版本之后移除)、NIO、APR.而且在Tomcat 8之后新增了对NIO2和HTTP/2协议的支持.因此,对协议和I/O进行抽象和建模是需要重点关注的.

Tomcat的设计方案如图2-13所示.

Tomcat总体架构(二)

在Tomcat中,ProtocolHandler表示一个协议处理器,针对不同协议和IO方式,提供了不同的实现,如: Http11NioProtocol表示基于NIO的HTTP协议处理器.

ProtocolHandler包含一个Endpoint用于启动Socket监听,该接口按照IO方式进行分类实现,如: Nio2Endpoint表示非阻塞式Socket I/O.

还包含一个Processor用于按照指定协议读取数据,并将请求交由容器处理,如: Http1lNioProcessor表示在NIO的方式下HTTP请求的处理类.

注意 Tomcat并没有Endpoint接口,仅有AbstractEndpoint抽象类,此处仅作为概念讨论,故将其视为Endpoint接口.

在Connector启动时,Endpoint会启动线程来监听服务器端口,并在接收到请求后调用Processor进行数据读取.具体过程见后续再讲.

当Processor读取客户端请求后,需要按照请求地址映射到具体的容器进行处理,这个过程即为请求映射.由于Tomcat各个组件采用通用的生命周期管理,而且可以通过管理工具进行状态变更,因此请求映射除考虑映射规则的实现外,还要考虑容器组件的注册与销毁

Tomcat通过Mapper和MapperListener两个类实现上述功能.前者用于维护容器映射信息,同时按照映射规则(Servlet规范定义)查找容器.后者实现了ContainerListener和LifecycleListener,用于在容器组件状态发生变更时,注册或者取消对应的容器映射信息.为了实现上述功能,MapperListener实现了Lifecycle接口,当其启动时(在Service启动时启动),会自动作为监听器注册到各个容器组件上,同时将已创建的容器注册到Mapper.

注意﹑在Tomcat7及之前的版本中,Mapper由Connector维护,而在Tomcat 8中,改由Service维护, 因为Service本来就是用于维护Connector和Container的组合,两者从概念上讲更密切一些.

Tomcat通过适配器模式(Adapter )实现了Connector与Mapper、Container的解耦.Tomcat默认的Connector实现(Coyote )对应的适配器为CoyoteAdapter.也就是说,如果你希望使用Tomcat的链接器方案,但是又想脱离Servlet容器(虽然这种情况几乎不可能出现,但是从架构可扩展性的角度来讲,还是值得讨论一下),此时只需要实现我们自己的Adapter即可.当然 我们还需要 按照Container的定义开发我们自己的容器实现(不一定遵从Servlet规范).

按照上述描述,Connector设计如图2-14所示.

Tomcat总体架构(二)

十、Executor

完成了Connector的设计之后,我们再进一步审视一下当前的应用服务器方案,很明显,我们忽略了一个问题——并发.这对应用服务器而言是尤其需要考虑的,我们不可能让所有来自客户端的请求均以串行的方式执行.那么,我们应如何设计应用服务器的并发方案?

首先,既然Tomcat提供了一致的可插拔的组件环境,那么我们自然也希望线程池作为一个组件进行统―管理.因此,Tomcat提供了Executor接口来表示一个可以在组件间共享的线程池(默认使用了JDK5提供的线程池技术),该接口同样继承自Lifecycle,可按照通用的组件进行管理.

其次,线程池的共享范围如何确定?

在Tomcat中Executor由Service维护,因此同一个Service中的组件可以共享一个线程池.

当然,如果没有定义任何线程池,相关组件(如Endpoint)会自动创建线程池,此时,线程池不再共享.

在Tomcat中,Endpoint会启动一组线程来监听Socket端口,当接收到客户端请求后,会创建请求处理对象,并交由线程池处理,由此支持并发处理客户端请求.这里我们仅从概念层面进行描述,Tomcat具体的线程池实现、使用方式、注意事项等会在后续详细描述.

添加Executor后,总体设计如图2-15所示.

Tomcat总体架构(二)

十一、Bootstrap 和 Catalina

我们在前面几个小节中讲解了Tomcat总体架构中的主要核心组件,它们代表了应用服务器程序本身,这就如楼房的主体.但是,除了主体建筑外,楼房还需要外墙等装饰,Tomcat也一样,我们还需要提供一套配置环境来支持系统的可配置性,便于我们通过修改相关配置来优化应用服务器.

当然,我们没有涉及集群、安全等组件,尽管它们也非常重要,但是,我们还是希望更多地关注于一些通用概念.虽然集群、安全等作为一个完备的应用服务器必不可少,但是它们的缺失并不会影响我们去理解应用服务器的基本概念和设计方式.这些内容将会在后续有机会再详细讲解.

在第1章中,我们列举了Tomcat的几个重要配置文件,其中最核心的文件为server.xml.通过这个文件,我们可以修改Tomcat组件的配置参数甚至添加相关组件,这也是后续性能调优阶段重点涉及的文件.Tomcat通过类Catalina提供了一个Shell程序,用于解析server.xml创建各个组件,同时,负责启动、停止应用服务器(只需要启动Tomcat顶层组件Server即可).Tomcat使用Digester解析XML文件,包括server.xml以及web.xml等,具体可参见http://commons.apache.org/proper/commons-digester/.

最后,Tomcat提供了Bootstrap作为应用服务器启动人口.Bootstrap负责创建Catalina实例,根据执行参数调用Catalina相关方法完成针对应用服务器的操作(启动、停止).

也许你会有疑问,为什么Tomcat不直接通过Catalina启动,而是又提供了Bootstrap呢?你可以查看一下Tomcat的发布包目录,Bootstrap并不位于Tomcat的依赖库目录下(SCATALINA_HOME/lib ),而是直接在SCATALINA_HOME/bin目录下.Bootstrap与Tomcat应用服务器完全松耦合(通过反射调用Catalina实例),它可以直接依赖JRE运行并为Tomcat应用服务器创建共享类加载器,用于构造Catalina实例以及整个Tomcat服务器.

注意Tomcat的启动方式可以作为非常好的示范来指导中间件产品设计.它实现了启动入口与 核心环境的解耦,这样不仅简化了启动(不必配置各种依赖库,因为只有独立的几个API ),而且便于我们更灵活地组织中间件产品的结构,尤其是类加载器的方案,否则,我们所有的依赖库将统一放置到一个类加载器中,而无法做到灵活定制.

Tomcat总体架构(二)

上述是Tomcat标准的启动方式.但是正如我们所说,既然Server及其子组件代表了应用服务器本身,那么我们就可以不通过Bootstrap和Catalina来启动服务器.

Tomcat提供了一个同名类org.apache.catalina.startup.Tomcat,使用它我们可以将Tomcat服务器嵌入到我们的应用系统中并进行启动.当然,你可以自己编写代码来启动Server,也可以自定义其他配置方式启动,如YAML.这就是Tomcat灵活的架构设计带给我们的便利,也是我们设计中间件产品的架构关注点之一.

十二、组件总结

最后,我们再整体回顾一下上述讲解涉及的Tomcat服务器中的概念,如表2-2所示.

表2-2 Tomcat的组件说明

组件名称 说明 Server 表示整个Servlet容器,因此Tomcat运行环境中只有唯一一个Server实例. Service 表示一个或者多个Connector的集合,这些Connector共享同一个Container来处理其请求.在同一个Tomcat实例内可以包含任意多个Service实例,它们彼此独立. Connector 即Tomcat链接器,用于监听并转化Socket请求,同时将读取的Socket请求交由Container处理, Connector 支持不同协议以及不同的I/O方式. Container Container表示能够执行客户端请求并返回响应的一类对象.在Tomcat中存在不同级别的容 Container 器:Engine、Host、Context、Wrapper. Engine Engine表示整个Servlet引擎.在Tomcat中,Engine为最高层级的容器对象.尽管Engine不是直 Engine 接处理请求的容器,却是获取目标容器的入口. Host Host作为一类容器,表示Servlet引擎((即Engine)中的虚拟机,与一个服务器的网络名有关, Host 如域名等.客户端可以使用这个网络名连接服务器,这个名称必须要在DNS服务器上注册. Context Context作为一类容器,用于表示ServletContext,在Servlet规范中,一个ServletContext即表示 Context 一个独立的Web应用. Wrapper Wrapper作为一类容器,用于表示Web应用中定义的Servlet Executor 表示Tomcat组件间可以共享的线程池

N、结束

Original: https://www.cnblogs.com/xysgo/p/16619322.html
Author: 菜阿
Title: Tomcat总体架构(二)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/601516/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • asyncio 异步编程

    首先了解一下协程,协程的本质就是一条线程,多个任务在一条线程上来回切换,协程的所有切换都是基于用户,只有在用户级别才能感知到的 IO 才会用协程模块来规避,在 python 中主要…

    数据库 2023年6月9日
    060
  • Javaer 面试必背系列!超高频八股之三色标记法

    可达性分析可以分成两个阶段 根节点枚举 从根节点开始遍历对象图 前文提到过,在可达性分析中,第一阶段 “根节点枚举” 是必须 STW 的,不然如果分析过程中…

    数据库 2023年6月6日
    0104
  • 达梦产品技术支持培训-day6-DM性能诊断与优化

    (本文只作为个人随笔用途,非官方文档,请勿作他用,谢谢) 1、DM8查询优化基本思路 1.1 操作系统性能诊断 linux常用系统监控命令 top 主要关注DMserver 的CP…

    数据库 2023年6月11日
    074
  • MIT 6.824 Lab2D Raft之日志压缩

    书接上文Raft Part C | MIT 6.824 Lab2C Persistence。 实验准备 实验代码: git://g.csail.mit.edu/6.824-gola…

    数据库 2023年6月14日
    0153
  • IntelliJ IDEA 断开svn连接

    1 设置菜单 2 进入pluglns 菜单,点击 browse repositonries….. 3 搜索 svn disconnect,然后安装插件 4 安装插件后,…

    数据库 2023年6月6日
    0159
  • nginx反向代理proxy_pass url后加/和不加/的区别

    在nginx中配置proxy_pass反向代理时,当在后面的url加上了/,相当于是绝对根路径,则nginx不会把location中匹配的路径部分代理走;如果没有/,则会把匹配的路…

    数据库 2023年6月6日
    0120
  • Nginx 静态资源、跨域、Rewrite

    Nginx服务器基础配置实例 前面我们已经对Nginx服务器默认配置文件的结构和涉及的基本指令做了详细的阐述。通过这些指令的合理配置,我们就可以让一台Nginx服务器正常工作,并且…

    数据库 2023年6月6日
    0139
  • 3 访问修饰符public,private,protected以及不写(默认)时的区别

    private 私有的,只对本类公开。 default 类的成员不写访问修饰符时默认为default,默认对于同一个包中的其它类相当于公开(public),对于不是同一个包中的其它…

    数据库 2023年6月6日
    089
  • 贪心算法原理及其应用

    概述 贪心算法应该算是那种”只闻其声不见其人”的算法,我们可能在好多地方都会听到贪心算法这一概念,并且它的算法思想也比较简单就是说算法只保证局部最优,进而达…

    数据库 2023年6月11日
    0145
  • MySQL数据库性能优化

    前言 由于一些企业需要在本地部署系统(使用企业服务器部署系统,数据库也部署在同一台服务器上),本地部署的系统的服务器往往无法到达我们的云部署服务器,速度性能更差。尤其是在查询统计报…

    数据库 2023年5月24日
    0104
  • mqtt长连接报错32000

    背景 项目需要使用mqtt协议建立长连接,我是客户端,需要连上服务端同学的提供的地址;客户端使用的是paho提供的客户端sdk,如下: org.eclipse.paho org.e…

    数据库 2023年6月11日
    0119
  • 自己编写平滑加权轮询算法,实现反向代理集群服务的平滑分配

    学会了负载均衡算法,却没有用起来? 今天就来 实战一遍,感受下平滑加权轮询算法的魅力。 通过Java语言,自己编写的平滑加权轮询算法,结合线程池和Socket 网络编程等,实现了反…

    数据库 2023年6月6日
    0271
  • 精心总结十三条建议,帮你创建更合适的MySQL索引

    上篇文章讲到使用MySQL的Explain命令可以分析SQL性能瓶颈,优化SQL查询,以及查看是否用到了索引。 我们都知道创建索引可以提高查询效率,但是究竟如何创建索引呢? [En…

    数据库 2023年5月24日
    0104
  • 前端常用函数封装

    常用函数封装 获取某日期若干个工作日后的日期 * 参数: * time: [String] 给&#x5B9…

    数据库 2023年6月11日
    087
  • Spring(一)-初识 + DI+scope

    1、获取bean实例的三种方式 UTF-8 4.3.18.RELEASE 1.16.18 4.11 org.springframework spring-beans ${sprin…

    数据库 2023年6月16日
    078
  • JUC学习

    如何正确停止线程? 停止线程应该是一种通知协作的方式,比如interrupt,但是它仅仅是通知线程,线程拥有完全的自主权,根据自身业务来判断什么时候停止,因为如果选择立即停止就可能…

    数据库 2023年6月16日
    099
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球