详解主流Java应用服务器的工作原理及组件设计

刘光瑞 2018-03-06 17:13:55

本文根据DBAplus社群第138期线上分享整理而成

 

讲师介绍
 
今天所讲内容的大纲如下:
  1. Java应用服务器分类

  2. Servlet容器工作原理

  3. Tomcat组件及请求处理

 

 

一、Java应用服务器分类
 

 

大概从2000年以后,Java应用服务器的使用主要经历了3个阶段:J2EE应用服务器、Servlet容器及Web服务器、响应式微服务。

 

1、J2EE应用服务器

 

首先,是J2EE应用服务器。这部分在以Spring为主的without EJB的应用框架出现之前,基本上是企业应用开发的标配。这类应用服务器严格实现了J2EE规范,通过了SUN的相关认证,还支持标准的基于J2EE的应用开发,包括EJB、JMS、JTA、Servlet、JSF等等。

 

 

 

这类应用服务器有很强的配置和优化能力,非常适合集中式的大型企业级应用开发。这一类的产品情况如下:

 

 

 

像WebSphere这一类商用产品,是大型的银行、电信应用的主流服务器,甚至配合IBM的JVM实现,有着非常好的性能优势。

 

免费的诸如JBOSS,使用也非常广泛,作为商用企业级应用服务器的减配版。

 

企业级应用服务器的特点就是过重。大概2010年之后(具体时间不确定),有几个转向了OSGi这种模块化架构,期望提供一种更加灵活的架构。像JBoss这一类产品在互联网规模化、轻量化的大背景下,似乎使用的相对越来越少。但是在集中式的企业级应用场景,它们应该还是主流,轻量级服务器是很难取代的。

 

2、Servlet容器及Web服务器

 

这一类在后面才提到,并不是说它们的历史较短,像Tomcat的历史可以说是最长的几款服务器实现。而是说它们被大规模用于生产环境的尝试,相对较晚。

 

这是与互联网应用大规模分布式架构分不开的。在轻量级的架构中,绝大多数应用实际上主要使用了服务器的Servlet容器部分,其它部分很少使用,而是使用更完善的第三方框架去替代。这时候,J2EE服务器显得过重,所以开始更倾向于只使用Servlet容器提供HTTP服务。

 

 

 

这一类服务器见图片,主要是Tomcat、Resin、Jetty、Undertow。

 

 

 

Tomcat大家想必都很熟悉了,基本Java Web应用开发入门就使用。它主要的使用场景还是独立启动部署Web应用,嵌入式的场景与Jetty相比,要相对差一些;Undertow出现相对晚一些,它是JBoss实现的新的Servlet容器,以前JBoss直接使用Tomcat作为Servlet容器。

 

如果大家基于Spring Boot开发应用,想必都知道,现在Spring Boot对这三款容器都支持,现在用的比较多的是Tomcat和Jetty,Undertow使用的相对较少。

 

后两者是很少作为独立启动的服务器来使用的,尤其是Undertow,应该不支持这种场景。

 

3、响应式微服务

 

这一类严格说并不是Web服务器。有时候(如微服务架构),我们更倾向于直接提供HTTP服务,这个时候并不需要支持Web,不需要Servlet规范。

 

 

 

这一类就是为这种场景设计的,目前有两类方案可以来满足这种场景,一种是Vert.x,这个国内目前使用应该不是很多。主要是它不支持Servlet规范,这估计是一方面的原因。

 

还有一种是Play Framework内置的基于事件的HTTP服务,和Vert.x类似,它们都是为高并发的互联网服务开发实现的。今天对于这种方案我们不做展开,还是以主流的Servlet方案为主。

 

 

二、Servlet容器工作原理
 

 

应用服务器基于Servlet提供基础的HTTP服务,包括Web服务、SOA,即便是JSP页面,最终也是通过Servlet实现的。因此Servlet容器可以说是Web服务的核心。

 

 

 

1、容器的核心部分

 

对于一款Servlet容器,主要分为两部分:链接器和容器。前者是服务器对I/O的封装,后者是对Servlet规范的实现。

 

 

 

大家可以简要看一下这一篇的内容:一款服务器通过链接器读取网络请求,将其转换为符合Servlet规范的Request对象,同时负责将Servlet容器的响应输出到客户端。

 

它对Servlet容器屏蔽了协议及I/O方式等的区别。无论是HTTP还是AJP、还是HTTPS,在容器中获取到的都是一个标准的Request对象。

 

 

 

而容器呢,主要负责三个方面的工作:

  • 按照Servlet规范(Web.xml/注解等等),识别部署的Web应用,将其解析为内部的请求处理组件;

  • 接收链接器的请求调用(一般不需要容器负责请求映射,映射部分是链接器完成的);

  • 按照Servlet规范进行请求处理,这部分主要是Filter等的处理。

 

 

 

2、工作原理

 

大家可以看一下这个示意图:

 

 

 

对于链接器,无论采用哪一种I/O,都是采用一种线程池的方式来处理的。

 

主要有两个线程池,一个用于接收请求,一个用于处理请求。接收请求的线程负责读取请求头,完成请求映射,并将请求提交到映射到的请求处理组件(一个指定的Servlet)。

 

这儿大家注意一点,容器映射完成的结果是一个Servlet,而不是一个Web应用,至少Tomcat是这样处理的,Jetty更轻量。

 

虽然Servlet的具体实现不同,但是完成的工作基本类似,而且模式也类似(除去我们说的第三类)。

 

接下来我们具体看一下Tomcat是如何实现Servlet容器的:相对于Jetty轻量的处理架构,Tomcat更像“服务器”一些,它考虑了一些服务器的特性,如虚拟主机。

 

 

三、Tomcat组件及请求过程
 

 

1、容器主要组件

 

Tomcat的主要组件可以看一下下面这张图:它底层通过Java命名服务统一对各种组件进行管理,体现了服务器软件的“管理”特性;然后是链接器实现Coyote和Jasper JSP引擎。

 

 

 

Tomcat与Jetty相比,它更是一款完整的Servlet容器方案。而Jetty是以模块化的形式提供了Servlet容器以及各种HTTP工具集。像JSP引擎,Jetty直接使用的Tomcat中的Jasper(还有另一种可选方案)。

 

最上面的Catalina是Tomcat容器的实现。大家如果看Tomcat的异常堆栈,就会发现包名基本都是以这个作为开头。

 

Tomcat链接器,我们可以简单的按两层进行划分:应用层和传输层。

 

 

 

应用层里,最新版本的Tomcat支持HTTP、AJP以及HTTP2,传输层支持NIO、NIO2以及APR,最新版本已经不支持BIO。

 

 

 

链接器还有一个很重要的组件就是线程池,想必做过基本的Tomcat优化的人都调整过Tomcat并发链接数。线程池的分类我们刚才也说过了,分为接收线程池和请求处理线程池。

 

2、Tomcat Servlet容器组件

 

Servlet容器的组件见图:简单来说,Tomcat通过一种分层的架构使得Servlet容器有很好的灵活性,而且通过生命周期管理接口,为这些各层的对象提供统一的生命周期管理。

 

 

 

分层如下:Engine-Host-Context-Wrapper。引擎是顶级,我们可以认为就是容器本身。Host是虚拟主机的概念,这样可以在Tomcat配置多个虚拟主机。Context就是我们的Web应用,Wrapper是Web应用中的Servlet,所以还是刚才说的,Tomcat中的请求映射是体现到Servlet上。

 

基于分层的组件,Tomcat还提供了完善的生命周期事件处理机制,Web应用的自动扫描启动、热部署、卸载等都是通过事件实现的。如果阅读Tomcat代码,需要重点关注下面的生命周期监听。

 

 

HostConfig主要用于扫描Web应用,自动根据Web应用目录创建Context。ContextConfig重点处理Servlet规范,生成Servlet、Filter等各种规范组件。最后,Tomcat自身也实现了一种链式的请求处理机制,这一点和规范中的Filter类似,这就是Valve。

 

在Tomcat中,Filter中可以做的事情,Valve可以做;Valve可以做的事情,Filter却不一定可以做,需要深入和服务器交互的工作,一般都是用Valve实现。

 

3、请求处理过程

 

这是一张Tomcat请求处理过程的时序图,大家看一下就会发现。Tomcat在这种分层的容器设计方案下,请求处理是很简单的,重点在前期的请求接收及I/O处理(Processor的各种实现)、Mapper的映射以及Wrapper中的规范处理。

 

 

 

4、Tomcat的优化

 

最后一点内容,我们再概要介绍一下Tomcat的优化。今天介绍的相对比较基础,大家感兴趣的还是需要去看相关的专门的书籍。

 

Tomcat优化主要但不限于以下四个方面:线程池、协议、JVM以及I/O,之所以说不限于,是因为应用服务器的优化,不仅要去优化其自身,诸如操作系统等也是要统一考虑的。

 

 

 

线程池就是我们刚才说的两类。接收线程池一般都是CPU内核数即可。当然如果服务器核数非常多,可以适当调小。这个线程池处理速度比请求处理线程池要快的多,因此要考虑这两者的匹配,原因PPT中都介绍了。

 

 

 

做请求处理线程池这个设置时,最好做一些基准测试,并没有什么精确的经验值。

 

协议层面。这个最常见的就是开启HTTP GZip压缩。如果配置了前置的Web服务器,那么静态文件可以考虑直接放到Web服务器上,然后可以修改HTTP链接器的I/O方式。新版本的Tomcat默认是NIO,可以改为APR,后者可以充分发挥本地调用的优势,与直接使用Apache基本是类似的。

 

 

 

当然对于前置了Web服务器的,可以采用AJP协议与Web服务器链接。Apache应该没问题,Nginx好像没有官方的AJP实现。第三方的就需要考虑健壮性和支持程度了。

 

 

然后是JVM,这部分估计最常见的就是调整JVM的堆内存。对于垃圾回收算法,也需要根据具体的应用场景进行调整。是吞吐量优先还是响应时间优先,需要使用多少线程去处理,这些都需要在具体的硬件配置下进行具体的测试才行。

 

 

 

今天的内容到此就结束了,主要内容还是非常概要,每一部分大家如果想深入了解,都需要去阅读相关书籍。协议、规范、JVM,这些都是必不可少的。

最新评论
访客 2023年08月20日

230721

访客 2023年08月16日

1、导入Mongo Monitor监控工具表结构(mongo_monitor…

访客 2023年08月04日

上面提到: 在问题描述的架构图中我们可以看到,Click…

访客 2023年07月19日

PMM不香吗?

访客 2023年06月20日

如今看都很棒

活动预告