服务器 : Apache Tomcat - 理解架构层次

Posted Rudolph_Browne

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了服务器 : Apache Tomcat - 理解架构层次相关的知识,希望对你有一定的参考价值。

 文章概览

  相信很多接触java的人都对Tom猫有着多少的熟悉,就个人而言,本来只知道Tom简单的操作与配置,就像裹上一层纱,迷迷糊糊的.

  Tomcat的书籍本来就不多,高分的还是很久之前的版本,直到最近看到下面这本书,解答了我的很多疑问,同时这篇文章将总结读书收获.

  如果觉得文章写的内容是你感兴趣的或者我的猫使你感兴趣,建议你读读这本书.

  

 

  该文会介绍Tom的架构,服务器如何从一层层抽象设计到完整的架构

Tomcat介绍

  Tom是一款全世界著名的轻量级应用服务器,基于java,服务于java.主要作为应用服务器来处理客户端发来的动态资源响应. 

  目前版本是9.x,很多人都在使用6.x,但新版其实提供了很多新的功能,比如WebSocket的支持,点击了解WebSocket

    

  Tomcat启动参数

    windows修改$CATALINA_HOME/bin/catalina.bat文件

    Set JAVA_OPTS=-server -Xms1024m -Xmx2048m -XX:PermSize=256m -XX:MaxPermSize=512m

    linux修改$CATALINA_HOME/bin/catalina.sh文件

    JAVA_OPTS="-server -Xms1024m -Xmx2048m -XX:PermSize=256m -XX:MaxPermSize=512m"

    -server  Server端启动Tomcat,Client启动Tomcat两者的初始化参数会有所不同

    -Xms1024m 初始化堆内存大小

    -Xmx2048m 允许的最大堆内存大小

    -XX:PermSize=256m 初始化非堆内存大小

    -XX:MaxPermSize=512m 允许的最大非堆内存大小

  Debug方式

    依赖于JDK提供的JPDA(Java Platform Debugger Architecture,Java平台调试体系)

    catalina jpda start

  目录结构

    

 

    该文使用的版本是apache-tomcat-9.0.1,目录大致都很容易理解.

    conf放置的Tomcat的核心配置文件,下文会介绍.

    webapps是默认的web app的应用目录,只要把项目目录放置进去就可以运行

    work是Tomcat运行时产生的jsp编译文件所存放的位置

 总体架构

  Tomcat是一款应用服务器,我们从最根本的类一层一层演变,直至Tomcat当前版本.

1.应用服务器是接收其他计算机(客户端)发来的请求数据并对其解析,完成相关业务处理,然后把处理结果作为响应返回给计算机.

  

2.一个问题摆在眼前,前面Server请求监听和请求处理放在一起,应用服务器通常会与web服务器进行集群部署和负载均衡,但是这两者的协议并不是HTTP.

也就是说,服务器连接的另一端需要适配不同的协议来对请求作出不同的处理.前面的模型扩展性太差,应该分离请求监听和请求处理.

Connector模块管理请求监听,Container模块负责请求处理,两个组件都拥有start()和stop()来加载和释放自己维护的资源.

这样子,Server下可以有多个Connector来传送请求至不同的Container中.

3.上面的设计有个缺陷,既然server可以有多个Connector和Container,那么如何知道哪个Connector将请求发至哪个Container呢?

考虑一下下面这个设计图,

4.我们接触过的Tomcat应该是放置web app的容器,在哪放置web app?这将决定哪个app来处理Engine所获取的请求信息.

  再想一下,我们浏览器是个app,对吧?然后服务器其实也是app对吧?网络就是两者的通信.我们来看一下网络是怎么进行通信的.

没错,web app需要端点信息(IP地址,端口号),我们需要提供这一层的抽象.一个Host下可以对应有多个app(Context).

 

5.现在设计已经可以满足两个应用的连接了,现在设想一下,应用该怎么进行表示?毕竟Tomcat作为一款Servlet容器而存在.首先Apache组织按照Servlet官方的标准,加入了Servlet的包装类Wrapper.

6.就目前为止,我们使用"容器"这一概念来形容处理接收客户端的请求并且返回响应数据的组件,依此,使用一个类Container来统一表示这一想法,让Engine,Host,Context,Wrapper这类组件来继承Container.

Container类能够添加子组件addChild方法,有时需要执行一些异步处理,所以加入backgroundProcess方法.

由于Engine,Host,Context,Wrapper这类的引用变成了父类Container,所以之前的强组合关系变成了弱组合关系.强弱关系指的是两个类直接关联或者是间接关联.

 7.为了从抽象和复用层面上再审视一下当前设计,使概念更加清晰,提供通用性定义.由于所有容器都有着自身的生命周期管理方法,那么我们可以将其进行抽象成一个接口Lifecycle,在方法定义上加入初始化方法init,销毁方法destroy,事件监听方法addLifecycleListener和removeLifecycleListener.

8.上面这个设计Container部分具有伸缩性和扩展性,这很棒.接下来Tomcat的开发人员为了提高每个组件的灵活性,使其更易扩展,加入了Pipeline和Valve这两个接口.这两个接口的设计运用了职责链模式.

简单介绍以下职责链模式,关于设计模式可以查看博客里的文章《软件设计 : 聚焦设计模式》

职责链模式使用一个抽象类来统一定义处理器,然后将处理器构造成一条链,当Client端发来请求时,第一个Handler判断是否处理,不处理则往下个Handler传递,直至被处理或则处理链结束.

回头看Tomcat怎么运用这个设计模式,Pipeline接口用于构建职责链,Valve接口代表职责链上的每个处理器.

Pipeline中维护一个基础的Valve,它始终位于Pipeline执行链的末端,封装了具体的请求处理和输出响应过程.

这样就可以构造一条职责链,可是为什么要这么做?记得之前Container不就是可以自包含的容器吗?为什么要弄出多两个接口?

的确,Container可以自包含,但是它是作为容器抽象类而存在,而阀(Valve)作为接口而存在,我们可以在实现这个接口的类中添加属于我们自己的Valve实现类,你想做什么都行.

  就像水管一样,你如果是超级马里奥,你可以随时给它加个阀,做任何事.

9.前面的设计基本落在Container部分,来看看Connector的设计方案,Connector必须完成下面的功能项.

  ①监听服务器端口,读取客户端的请求

  ②将请求数据按指定协议进行解析

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

  ④将请求返回客户端

Tomcat支持多协议(HTTP/AJP)和多种IO方式(BIO,NIO,NIO2,APR,HTTP/2)

  

ProtocolHandler表示协议处理器,针对不同的协议和IO方式,提供不同的实现,ProtocolHandler包含一个Endpoint用来启动socket监听,该接口按照IO方式进行分类实现,还包含一个Process用于按照指定协议读取数据,并交由容器处理.

处理逻辑如下:

1.在Connector启动时,Endpoint会启动线程来监听服务器端口,并在接收到请求后调用Process进行数据读取.

2.当Process读取客户端请求之后,需要按照地址映射到具体的容器进行处理,即请求映射.

3.由于Tomcat各个组件采用通用的生命周期进行管理,而且通过管理工具进行状态变更,因此请求映射除了考虑映射规则的实现外,还要考虑容器组件的注册和销毁.

Tomcat采用Mapper来维护容器映射信息,按照映射规则(Servlet规范定义)查找容器;

MapperListener实现LifecycleListener和ContainerListener,用于在容器组件状态变更时,注册或者取消对应的容器映射信息;

MapperListener实现了Lifecycle接口,当Service启动时,会自动作为监听器注册到各个容器组件之上,同时将已创建的容器注册到Mapper;

Tomcat通过适配器模式实现了Connector与Mapper,Container的解耦,默认实现为CoyotoAdapter;

10.到这里,服务器可以正常接入请求和完成响应,可是我们还没考虑到一个关键的问题——并发

Tomcat使用组件式的设计理念,那么也会有并发组件.

Tomcat组织为此提供了一个Executor接口表示一个可以在组件间共享的线程池,该接口同样继承自Lifecycle接口,按照通用组件进行管理.

Executor由Service进行维护,因此同一个Service中的组件共享一个线程池.值得注意的是如果没有定义线程池,相关组件会自动创建线程池,此时线程池不再共享.

在Tomcat中,Endpoint会启动一组线程来监听Socket端口,当接收到客户请求会创建请求处理对象,并交由线程池处理,由此支持并发处理客户端请求.

11.现在Tomcat基础的核心组件已经完整了,但是架构其实还有很多组件没有显示出来.Tomcat开发人员为了让使用者很好地使用Tomcat,提供了一套配置环境来支持系统的可配置性——Catalina.

Catalina代表了整个Servlet容器架构,包含了上面所有组件,还有还没谈及的安全,会话,集群,部署,管理等Servlet容器组件.它通过松耦合的方式集成了Coyoto,以完成按照请求协议进行数据读写.同时,还包括启动入口、Shell程序等.

Bootstrap是Catalina的启动入口.

为什么Tomcat不通过Catalina启动,而又提供了Bootstrap?

查看一下Tomcat发布包目录,Bootstrap并不存放于Catalina的lib目录下,而是置于bin目录中.Bootstrap通过反射调用Catalina实例,与Tomcat服务器完全松耦合,它可以直接依赖JRE运行并为Tomcat应用服务器创建共享类加载器,用于构建Catalina实例以及整个Tomcat服务器.

 

至此,Tomcat的基础核心组件介绍结束,我们回顾一下组件的概念

Server   表示整个Servlet容器,一个Tomcat运行环境只存在一个Server,可存在多个Service.

Service    表示链接器和处理器的集合,同一个Service下的链接器将请求传至该Service下的处理器

Connector 表示链接器,用于监听并转化Socket请求,支持不同协议与IO方式

Container  表示容器组件,能执行客户端请求并返回响应的组件

Engine    表示顶级容器,是获取目标容器的入口

Host   表示Servlet引擎中的虚拟机,提供Host之类的域名信息

Context  表示一个web app应用上下文环境

Wrapper 具体的Servlet包装类

Executor   组件间共享的线程池

Tomcat启动与请求响应

 

Tomcat类加载器

应用服务器通常会自行创建类加载器以实现更加灵活的控制,这是对规范的实现(Servlet规范要求每个Web应用都有独立的类加载器实例),也是架构层面的考虑.

书中p46对类加载器进行了详细说明

JVM默认提供了三个类加载器来进行类加载,Tomcat在加载器上进行扩展,用来加载应用自身的类.

Bootstrap  JVM提供,加载JVM运行的基础运行类,即位于%JAVA_HOME%/jre/lib目录下的核心类库

Extension    JVM提供,加载%JAVA_HOME%/jre/lib/ext目录下的扩展类库

System      JVM提供,加载CLASSPATH指定目录下或者-classpath运行参数指定的jar包

         Tomcat的Bootstrap类即由这个加载器载入

Common   以System为父类加载器,是Tomcat应用服务器顶层的公用类加载器,

         其路径common.loader,默认指向$Catalina_Home/lib目录.

Catalina     用于加载Tomcat应用服务器的类加载器,路径为server.loader,

         默认为空,此时Tomcat使用Common类加载器加载应用服务器.

Shared    所有Web应用的类加载器,路径为shared.loader,默认为空.

         此时使用Common类加载器作为Web应用的父加载器.

Web App   加载WEB-INF/classes目录下未压缩的Class和资源文件以及/WEB-INF/lib目录下的jar包.

        该类加载器对当前web应用可见,对其他web应用不可见. 

 

 

 

 

 

以上是关于服务器 : Apache Tomcat - 理解架构层次的主要内容,如果未能解决你的问题,请参考以下文章

服务器 : Apache Tomcat - 理解架构层次

十台服务器集群架构图

初学者如何理解Tomcat?

初学者如何理解tomcat服务器?

Apache反向代理结合Tomcat集群来实现负载均衡概念理解

深入理解Tomcat系列之一:系统架构