容器组件-Tomcat

Tomcat简介

Tomcat是全世界最著名的基于Java的轻量级应用服务器, 是一款完全开源免费的Servlet容器实现. 同时, 它支持Html、JS等静态资源的处理, 因此又可以作为轻量级Web服务器使用.

Tomcat启动脚本

配置修改

可以通过编辑$CATALINA_HOME/bin/catalina.sh, 修改Tomcat启动配置:

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

启动容器

1
[unzone@localhost ~ ]$ $CATALINA_HOME/bin/startup.sh

Tomcat目录说明

Tomcat新特性

  • 自8.0版本开始, Tomcat支持Servlet3.1、JSP2.3、EL3.0、WebSocket1.1; 并且9.0版本开始支持Servlet4.0

  • 自8.0版本开始, 默认的HTTP、AJP链接器采用NIO、而非Tomcat7以前版本的BIO;并且从8.5开始, 移除了对BIO的支持

  • 自8.0版本开始, 链接器新增支持JDK7的NIO2

  • 自8.0版本开始, 链接器新增支持HTTP/2协议.

  • 在自8.0版本中, Tomcat提供了一套全新的资源实现, 采用单独、一致的方法配置Web应用的附件资源.

Tomcat架构

Tomcat组成模块

组件说明

  • Server: 表示整个Servlet容器, 因此Tomcat运行环境中只有唯一一个Servlet实例

  • Service: Service表示一个或者多个Connector的集合, 这些Connector共享同一个Container来处理其请求, 在同一个Tomcat实例内可以包含任意多个Service实例, 他们彼此独立

  • Connector: 即Tomcat链接器, 用于监听并转换Socket请求, 同时将读取的Socket请求交由Container处理, 支持不同协议以及不同的I/O方式

  • Container: Container表示能够执行客户端请求并返回响应的一类对象. 在Tomcat中存在不同级别的容器: Engine、Host、Context、Wrapper

  • Engine: Engine表示整个Servlet引擎. 在Tomcat中, Engine为最高层级的容器对象, 尽管Engine不是直接处理请求的容器, 却是获取目标容器的入口.

  • Host: Host作为一类容器, 表示Servlet引擎(即Engine)中的虚拟机, 与一个服务器的网络名有关, 如域名等. 客户端可以使用这个网络名连接服务器, 这个名称必须要在DNS服务器上注册.

  • Context: Context作为一类容器, 用于表示ServletContext, 在Servlet规范中, 一个ServletContext即表示一个独立的Web应用.

  • Wrapper: Wrapper作为一类容器, 用于表示Web应用中定义的Servlet

  • Executor: 表示Tomcat组件间可以共享的线程池

Tomcat类加载器

Java类加载器

JVM默认提供3个类加载器, 他们以一种父子树的方式创建, 同时使用委派模式确保应用程序可以通过自身的类加载器加载所有可见的Java类.

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

  • Extension: Java提供的一个标准的扩展机制用于加载除核心类库外的Jar包, 即只要复制到指定扩展目录(可以多个)下的Jar, JVM会自动加载(不需要通过-class指定), 默认的扩展目录是%JAVA_HOME%/jre/lib/ext.

  • System: 用于加载环境变量CLASSPATH(不推荐使用)指定目录下的或者-classpath运行参数指定的JAR包, System类加载器通常用于加载应用程序Jar包以及其启动入口类(Tomcat的Bootstrap类即由System类加载器加载)

双亲委派模式

Java默认的类加载机制是委派模式, 委派过程如下:

  1. 从缓存中加载
  2. 如果缓存中没有, 则从父亲加载器中加载
  3. 如果父亲加载器中没有, 则从当前类加载器加载
  4. 如果没有,则抛出异常

Tomcat类加载器

Tomcat除了每个Web应用的类加载器外, 也提供了3个基础的类加载器和Web应用类加载器. 而且这3个类加载器指向的路径和包列表均可以由catalina.propertis配置

  • Common: 以System为父类加载器, 是位于Tomcat应用服务器顶层的公用类加载器, 其路径为common.loader, 默认指向$CATALINA_HOME/lib下的包.

  • Catalina: 以Common为父加载器, 是用于加载Tomcat应用服务器的类加载器, 其路径为server.loader, 默认为空, 此时Tomcat使用Common类加载器加载应用服务器.

  • Shared: 以Common为父加载器, 是所有Web应用的父加载器, 其路径为shared.loader, 默认为空, 此时Tomcat使用ommon类加载器作为Web应用的父加载器.

  • Web应用: 以Shared为父加载器, 加载/WEB-INF/classes目录下的未压缩的Class和资源文件以及/WEB-INF/lib目录下的包, 该类加载器只对当前Web应用可见, 对其它Web应用均不可见.

Web应用类加载顺序

Tomcat提供了delegate属性用于控制是否启用Java委派模式, 默认为false(不启用), 当配置为true时, Tomcat将使用Java默认的委派顺序.

delegate=false(默认)

Tomcat提供的Web应用类加载器与默认的委派模式稍有不同, 当进行类加载时, 除JVM基础类库外, 它会首先尝试通过当前类加载器加载, 然后才进行委派. 过程如下:

  1. 从缓存中加载
  2. 如果缓存中没有, 则从JVM的Bootstrap类加载器加载
  3. 如果没有, 则从当前加载器加载(按照WEB-INF/classes、WEB-INF/lib的顺序)
  4. 如果没有, 则从父类加载器加载, 由于父类加载器采用默认的委派模式, 所以加载顺序为System、Common、Shared。

delegate=true

  1. 从缓存中加载
  2. 如果没有, 则从JVM的Bootstrap类加载器加载
  3. 如果没有, 则从父类加载器加载, 加载顺序为System、Common、Shared。
  4. 如果没有, 则从当前加载器加载(按照WEB-INF/classes、WEB-INF/lib的顺序)

Tomcat请求处理

过程描述

1、用户点击网页内容,请求被发送到本机端口8080(默认),被在那里监听的Coyote HTTP/1.1 Connector获得.

2、Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应.

3、Engine获得请求localhost/test/index.jsp,匹配所有的虚拟主机Host。

4、Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机)名为localhost的Host获得请求/test/index.jsp,匹配它所拥有的所有的Context。Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为“ ”的Context去处理).

5、path=“/test”的Context获得请求/index.jsp,在它的mapping table中寻找出对应的Servlet。Context匹配到URL PATTERN为*.jsp的Servlet,对应于JspServlet类.

6、构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet()或doPost().执行业务逻辑、数据存储等程序。

7、Context把执行完之后的HttpServletResponse对象返回给Host.

8、Host把HttpServletResponse对象返回给Engine.

9、Engine把HttpServletResponse对象返回Connector.

10、Connector把HttpServletResponse对象返回给客户Browser

Tomcat集群

集群增量会话管理器 DeltaManager

DeltaManager会话管理器是tomcat默认的集群会话管理器,它主要用于集群中各个节点之间会话状态的同步维护.

集群增量会话管理器的职责是将某节点的会话该变同步到集群内其他成员节点上,它属于全节点复制模式,所谓全节点复制是指集群中某个节点的状态变化后需要同步到集群中剩余的节点,非全节点方式可能只是同步到其中某个或若干节点.

集群备份会话管理器 BackupManage

全节点复制的网络流量随节点数量增加呈平方趋势增长,也正是因为这个因素导致无法构建较大规模的集群,为了使集群节点能更加大,首要解决的就是数据复制时流量增长的问题,于是tomcat提出了另外一种会话管理方式,每个会话只会有一个备份,它使会话备份的网络流量随节点数量的增加呈线性趋势增长,大大减少了网络流量和逻辑操作,可构建较大的集群.

BackupManager并不是基于SessionMessage实现的, 它的本地会话存储采用的是一个有状态的,可复制的Map-LazyReplicatedMap(它采用主从的备份策略), 通过这个Map, BackupManager仅会将会话的增量数据复制到一个备份节点, 集群中的所有节点均知晓该备份节点的位置.

集群替代方案

Tomcat集群组件多用于实现会话同步, 但是这种方案不适用于较大规模的集群. 除了通信开销外, 部署架构也显的比较复杂, 实际上, 在搭建负载均衡时, 完全可以将会话集中管理. 当规模较大时, 可以采用数据库(PersistentManager+JDBCStore); 当规模较大时, 可以采用高速缓存替代, 如Redis、Memcached. 此时需要自己实现Tomcat的会话管理器. 当前网上已经有非常多的实现方案,如redis-session-manager.

性能调优

性能检测工具

ApacheBench、JMeter

JVM调优概述

Tomcat是一款Java应用, 所以JVM的配置与其运行性能密切相关. JVM优化的重点则集中在内存分配以及GC策略调整上, 因为JVM垃圾回收机制会不同程度地导致程序运行中断.

垃圾回收性能度量

  • 吞吐量: 工作时间(排除GC时间)占总时间的百分比, 工作时间并不仅是程序运行的时间, 还包括内存分配的时间.

  • 暂停: 测量时间段内, 由垃圾回收导致的应用程序停止响应测次数.

垃圾区收集器选择

  • 串行收集器: (Serial Collector), 采用单线程执行所有垃圾回收, 适用于单核的服务器.

  • 并行收集器: (Parallel Collector), 又称吞吐量收集器, 以并行的方式执行年轻代垃圾回收, 它适用于在多处理器或者多线程硬件上运行的数据集为中大型的应用

  • 并发收集器: (Concurrent Collector), 以并发的方式执行大部分垃圾回收工作,以缩短垃圾回收的暂停时间, 它适用于那些数据集为中大型、响应时间优先于吞吐量的应用.

  • CMS收集器: (Concurrent Mark Sweep Collector, 并发标记扫描收集器) 适用于那些更愿意缩短垃圾回收暂停时间并且负担得起与垃圾回收共享处理资源的应用.

  • G1收集器: (Garbage-First Garbage Collector) 适用于大容量内存的多核服务器, 它在满足垃圾回收暂停时间目标的同时, 以最大可能性实现高吞吐量.

并行与并发

  • 并发是指一个处理器同时处理多个任务。

  • 并行是指多个处理器或者是多核的处理器同时处理多个不同的任务。

并发是逻辑上的同时发生,而并行是物理上的同时发生。
来个比喻:并发是一个人同时吃三个馒头,而并行是三个人同时吃三个馒头。

JVM相关参数说明

JVM通用选项说明

并行收集器性能相关选项

CMS收集器相关选项

G1收集器相关选项

打印输出相关选项

Tomcat配置

Tomcat容器相关的配置均在$CATALINA_BASE/conf/server.xml中.

  • 修改链接器的maxConnections属性, 该属性决定了服务器在同一时间接收并处理的最大连接数. 当达到该值后, 服务器接收但不会处理更多的请求, 额外的请求将会被阻塞直到连接数低于maxConnections, 此时,服务器将再次接收并处理新连接.

  • 将tcpNoDelay属性设置为true, 会开启Socket的TCP_NO_DELAY选项, 它会禁用Nagle算法. 该算法永不链接小的缓冲消息, 它会降低通过网络发送数据包的数量, 提升网络传输效率, 但是对于交互式应用(如Web)会增加影响时间.

  • 调整maxKeepAliveRequest属性值, 该属性用于控制HTTP请求的keep-alive行为, 指定了链接被服务器关闭之前可以接受的请求最大数目

  • 修改socketbuffer属性, 调整Socket缓冲区大小, 通过合理调整Socket缓冲器有助于提升服务器性能.

  • 将enableLookups属性设置为false, 禁用request.getRemoteHost的DNS查找功能, 减少查找时间.

  • 关闭自动部署功能(线上), 修改Host元素,将autoDeploy属性设置为false.

上面只是简单列出常见的一些调优参数, 更多的可以根据应用场景来选择调整.

应用性能优化

  • 尽量减少浏览器与服务器通信次数, 对于浏览器触发的远程操作, 尽量由一次调用完成.

  • 尽量减少请求响应数据量. 去除无用数据, 降低网络开销.

  • 尽量推迟创建会话的时机, 对于不需要会话的则尽量不要创建.

  • 不要再会话中存储大对象, 这会导致占用内存过多, 降低服务器性能.

  • 尽量缩短会话的有效期, 能够及时移除无效会话, 降低会话管理成本.

  • 合理定义对象作用域, 以便对象可以及时回收.

  • 采用连接池提升访问性能. 如数据库连接池.

  • 对于极少变更的数据, 可以采用考虑缓存提升查询性能.

  • 最小化应用日志, 或者尽量采用简单的日志格式. 生成环境避免生成大量非重要日志,建议只输出INFO及以上的日志.

参考文档