Tomcat

目录结构

目录及文件 说明
bin 用于存放Tomeat的启动、停止等批处理脚本和Shelll脚本
bin/startup.bat 用于在Vindows下启动Tomeat
bin/startup.sh 用于在Linux下启动Tomcat
bin/shutdown.bat 用于在Windows下停止Tomcat
bin/shutdown.sh 用于在Linux下停止Tomcat
conf 用于存放Tomeat的相关配置文件
conf/Catalina 用于存储针对每个虚拟机的Context配置
conf/context.xml 用于定义所有Wcb应用均需要加载的Context配置,如果Web应用指定了自己的context..xml,那么该文件的配置将被覆盖
conf/catalina.properties Tomcat环境变量配置
conf/catalina.policy 当Tomeat在安全模式下运行时,此文件为默认的安全策略配置
conf/logging.properties Tomcat日志配置文件,可通过该文件修改Tomcat日志级别以及日志路径等
conf/server.xml Tomcat服务器核心配置文件,用于配置Tomcat的链接器、监听端口、处理请求的虚拟主机等。可以说,Tomcat主要根据该文件的配置信息创建服务器实例
conf/tomcat-users.xml 用于定义Tomcat默认用户及角色映射信息,Tomcat的Manager模块即用该文件中定义的用户进行安全认证
conf/web.xml Tomcat中所有应用默认的部署描述文件,主要定义了基础Servlet和MME映射。如果应用中不包含web.xml,那么Tomcat将使用此文件初始化部署描述,反之,Tomcat会在启动时将默认部署描述与自定义配置进行合并
lib Tomcat服务器依赖库目录,包含Tomeat服务器运行环境依赖Jar包
logs Tomcat默认的日志存放路径
webapps Tomcat默认的Web应用部署目录
work web应用JSP代码生成和编译临时目录

部署方式

JAVA WEB项目目录结构

架构

classDiagram
    class Server {
        +start()
        +stop()
    }
    class Service {
        +start()
        +stop()
    }
    class Connector {
        +start()
        +stop()
    }
    class Container {
        +start()
        +stop()
    }
    Server *--> Service
    Service *--> Connector
    Service *--> Container

Connector

连接器需要完成 3 个的功能:网络通信、应用层协议解析、Tomcat Request/Response 与 ServletRequest/ServletResponse 的转化分别由 Endpoint、Processor 和 Adapter 完成

classDiagram
    class Connector {
        +start()
        +stop()
    }
    class ProtocolHandler {
        +handle()
    }
    class AbstractEndpoint {
        +bind()
        +release()
    }
    class Processor {
        +process()
    }
    Connector *--> ProtocolHandler
    ProtocolHandler *--> AbstractEndpoint
    ProtocolHandler ..> Processor
sequenceDiagram
  外部 ->> Endpoint: 请求
  note left of Endpoint: TCP/IP
  Endpoint ->> Processor: Socket
  note left of Processor: HTTP/AJP
  Processor ->> Adapter: Tomcat Request
  Adapter ->> Container: Servlet Request

Container

stateDiagram
    direction LR
    连接器 --> Engine
    Engine --> Host1
    Host1 --> Context1
    Context1 --> Wrapper1
    Context1 --> Wrapper2
    Host1 --> Context2
    Engine --> Host2

所有的容器组件都实现了 Container 接口,因此组合模式可以使得用户对单容器对象和组合容器对象的使用具有一致性。

Tomcat 通过 Mapper 组件保存了 Web 应用的配置信息,其实就是容器组件与访问路径的映射关系,当一个请求到来时,Mapper 组件通过解析请求 URL 里的域名和路径,就能定位到一个 Servlet。

一个请求流经 Engine -> Host -> Context -> Wrapper,每个节点都会对请求做一些处理

LifeCycle

容器相关的组件都实现了 LifeCycle 接口,父组件以此管理子组件的启动与停止

Executor

在 Endpoint 与 Processor 之间有个线程池来处理请求,共享线程池由Service维护

Bootstrap和Catalina

Bootstrap 是用来初始化类加载器的

通过 Bootstrap 启动 Cataina, Catalina启动Server 实现了Bootstrap 与 Server进行解耦

启动流程

sequenceDiagram
  用户 ->> Bootstrap: start
  Bootstrap ->> Bootstrap: init
  Bootstrap ->> Bootstrap: load
  Bootstrap ->> Cataina: load
  Cataina ->> Cataina: 创建Server
  Cataina ->> Server: init
  Server ->> Service: init
  Service ->> Engine: init
  Engine ->> Host: init
  Host ->> Context: init
  Service ->> Executor: init
  Service ->> Connector: init
  Connector ->> ProtocolHandler: init

  Bootstrap ->> Bootstrap: start
  Bootstrap ->> Cataina: start
  Cataina ->> Server: start
  Server ->> Service: start
  Service ->> Engine: start
  Engine ->> Host: start
  Host ->> Context: start
  Service ->> Executor: start
  Service ->> Connector: start
  Connector ->> ProtocolHandler: start

请求处理

sequenceDiagram
    participant Endpoint
    participant Processor
    participant CoyoteAdapter
    participant Mapper
    participant Engine
    participant Host
    participant Context
    participant Wrapper
    participant FilterChain
    participant Servlet

    Endpoint->>Processor: Request
    Processor->>CoyoteAdapter: Process request
    CoyoteAdapter->>Mapper: Map request
    Mapper->>Engine: Send to engine
    Engine->>Host: Identify host
    Host->>Context: Identify context
    Context->>Wrapper: Identify wrapper
    Wrapper->>Wrapper: Build Filters
    Wrapper->>FilterChain: Apply filters
    FilterChain->>Servlet: Forward to servlet

类加载器

stateDiagram-v2
  BootstrapClassLoader --> ExtensionClassLoader
  ExtensionClassLoader --> SystemClassLoader
  SystemClassLoader --> CommonClassLoader
  CommonClassLoader --> CatalinaClassLoader
  CommonClassLoader --> SharedClassLoader
  SharedClassLoader --> WebApp1ClassLoader
  SharedClassLoader --> WebApp2ClassLoader

通过每个app使用自己的类加载器来达到:

对于Web 应用类加载器,它的加载顺序:

线程上下文加载器:这个类加载器保存在线程私有数据里,只要是同一个线程,一旦设置了线程上下文加载器,在线程后续执行过程中就能把这个类加载器取出来用

热加载与热部署

周期性检测资源文件变化,热加载主要完成了下面这些任务:

  1. 停止和销毁 Context 容器及其所有子容器,子容器其实就是 Wrapper,也就是说 Wrapper 里面 Servlet 实例也被销毁了
  2. 停止和销毁 Context 容器关联的 Listener 和 Filter
  3. 停止和销毁 Context 下的 Pipeline 和各种 Valve
  4. 停止和销毁 Context 的类加载器,以及类加载器加载的类文件资源
  5. 启动 Context 容器,在这个过程中会重新创建前面四步被销毁的资源

当监听到webapps 目录变化,Host 会创新创建相对应的 Context 并启动

Context的命名与请求路径映射

基础文件名称 Name Path Version 部署文件名称
foo /foo /foo foo.xml、foo.war、foo
foo#bar /foo/bar /foo/bar foo#bar.xml、foo#bar.war、foo#bar
foo##2 /foo##2 /foo 2 foo##2.xml、foo#+2.war、foo#2
foo#bar##2 /foo/bar##2 /foo/bar 2 foo#bar##2.xml、foo#bar##2.war、foo#bar##2
ROOT ROOT.xml、ROOT.war、ROOT
ROOT##2 ##2 2 ROOT##2.xml、ROOT##2.war、ROOT##2

Catalina 自带的 Servlet

Coyote

支持的传输协议:

支持的IO方案:

HTTP 配置:

<!-- server.xml -->
<Connector executor="tomcatThreadPool"
               port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

<!-- 使用NIO方式处理HTTP1.1 -->
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true">
<!-- 
  maxThreads:指定Connector创建请求处理线程的最大数
  maxSpareThreads:允许空闲线程的最大数
  minSpareThreads
  tcpNoDelay:禁止TCP通过批量发送数据来提高网络利用率
  maxKeepAliveRequest: 最大keepalive的连接数
  socketBuffer
  enableLookups:是否开启request.getRemoteHost() DNS查询
 -->

概念

AJP

<!-- server.xml -->
<Connector protocol="AJP/1.3"
               address="::1"
               port="8009"
               redirectPort="8443" />

AJP(Apache JServ Protocol)是定向包协议。因为性能原因,使用二进制格式来传输可读性文本。WEB服务器通过 TCP连接 和 SERVLET容器连接

包结构:

字节位置 0     1      2 3           4-(n+3)
内容     0x12 0x34 数据长度(n)       数据

有效载荷的前一个字节代表类型

序号 类型 描述
2 Forward Request 使用接下来的数据开始请求处理周期
7 Shutdown Web服务器请求Servlet容器关闭自己
8 Ping Web服务器请求Servlet容器采取控制(安全登录阶段)
10 CPing Web服务器请求Servlet容器通过一个CPong快速响应
Data 主体数据及其大小
3 Send Body Chunk Servlet?容器向Web服务器发送一个主体数据块
4 Send Headers Servlet?容器向Web服务器发送响应头信息
5 End Response 用于标记响应结束
6 Get Body Chunk 如果请求数据未传输完,用于得到更多的请求数据
9 CPong Reply CPingi请求应答
sequenceDiagram
    title 请求处理
    participant Web服务器 as Web服务器
    participant Server程序 as Servlet容器
    Web服务器->>Server程序: Forward Request
    Server程序->>Web服务器: Data
    Web服务器->>Server程序: Get Body Chunk
    Server程序->>Web服务器: Data
    Web服务器->>Server程序: Send Headers
    Server程序->>Web服务器: Send Body Chunk
    Web服务器->>Server程序: End Response

Jasper

使用单独的类加载器

编译方式

sequenceDiagram
  title 运行时编译
  用户 ->> JspServlet: service()
  JspServlet ->> JspServlet: 获取JSP文件路径
  JspServlet ->> JspServlet: 判断当前请求是否为预编译请求
  JspServlet ->> JspServlet: 执行请求serviceJspFile()
  alt 找不到对应JspServletWrapper
    JspServlet ->> JspServletWrapper: 执行请求
    JspServletWrapper ->> JspComplicationContext: 开发环境或第一次调用,执行编译
    JspServletWrapper ->> JspServletWrapper: 重新加载并实例化JSP的Servlet类
    JspServletWrapper ->> JspServletWrapper: 更新上次使用时间
    JspServletWrapper ->> JspServletWrapper: 调用JSP Servlet执行请求
  end

编译结果:

预编译:

jspc

编译原理

// 继承该类
class index_jsp extends HttpJspBase
private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();
private static Map<String, Long> _jspx_dependants; // 依赖的外部资源
private static final Set<String> _jspx_imports_packages = new HashSet(); // 导入的包
private static final Set<String> _jspx_imports_classes; // 导入的类

_jspService 处理请求:

sequenceDiagram
  title JSP编译流程
  外部 ->> JspComplicationContext: 编译
  JspComplicationContext ->> Compiler: 是否过期
  Compiler ->> JspComplicationContext: 没过期直接返回
  opt 编译过程
    JspComplicationContext ->> Compiler: 删除生成的文件
    Compiler ->> ParserController: 解析JSP页面
    Compiler ->> Generator: 生成Java源码
    Compiler ->> Compiler: 生成Class文件
  end

Session机制

主要由每个 Context 容器内的一个 Manager 对象来管理 Session,默认为 StandardManager。通过 Request 获取 Session 会执行创建 Session,Session 存放在个 ConcurrentHashMap 中。

StandardContext 会有个定时扫描的线程去清理过期的 Session,Session 创建事件的监听则是通过 StandardContext 将 HttpSessionListener 类型的 Listener 取出,然后依次调用它们的 sessionCreated 方法。

配置管理

JVM配置

:: JVM启动参数
set "JAVA_OPTS=%JAVA_OPTS% %JSSE_OPTS%"

系统属性:略

服务器配置

catalina.properties: 容器启动阶段的配置

server.xml: 服务器核心配置

Web 应用配置

内置的 Filter

Tomcat 管理

/host-manager/html

集群

stateDiagram-v2
  LB --> WebServer1
  WebServer1 --> Tomcat1
  WebServer1 --> Tomcat2
  Tomcat1 --> Tomcat2
  Tomcat2 --> Tomcat1
  LB --> WebServer2
  WebServer2 --> Tomcat3
  WebServer2 --> Tomcat4
  Tomcat3 --> Tomcat4
  Tomcat4 --> Tomcat3

Tomcat 集群实现的原理就是在节点之间同步 Session

Tomcat 本身就不适合配置集群 一种通用的解决方案是 接入层为 Nginx

Nginx 对后端的Tomcat进行负载均衡

Tomcat上的Web应用最好是设计成无状态的 如果仍然需要保持会话 最好使用一台独立的服务器来存储会话 比如 Redis

而不要使用Tomcat的会话同步功能

安全

安装部署:下载安全 移除自带的几个Web应用

server.xml:

应用安全

传输安全(SSL)

JAVA安全策略

优化

JVM 优化

Tomcat 配置优化

server.xml:

网络传输优化:

JSP页面配置(web.xml):

继承 web 服务器:

应用优化

附加功能