Tomcat 不停机升级 Web 应用

1、概览

Apache Tomcat,简称 Tomcat,是 Jakarta Servlet 规范的开源实现。它作为 Web 服务器接收 HTTP 或 WebSocket 请求,并调用负责的 Servlet 来处理请求。

本文将带你了解 Tomcat 中的并行部署(Parallel Deployment),以及如何实现不停机升级 Web 应用。

2、Tomcat 部署模式

我们可以通过两种方式使用 Apache Tomcat 为 Web 应用提供服务。第一种方法是将 Tomcat 程序嵌入 Java 应用本身。或者,可以将 Apache Tomcat 作为一个专用的 Web 服务器进程运行,为一个或多个 Web 应用提供服务。在这种模式下,开发人员会将 Web 应用打包成 WAR (Web Application Archive,Web 应用归档)包。然后,Web 服务器管理员会将 Web 应用部署到 Tomcat Web 服务器上。

独立的 Tomcat 部署模式现在不怎么流行了,但是也有好处。多个不同的 Web 应用使用同一个 Tomcat 服务器会节省一定的资源。

将 Tomcat 作为独立 Web 服务器运行时,需要了解如何对运行中的 Web 应用进行不停机重新部署。与通常将此任务委托给 Kubernetes 等外部协调器的容器化 Web 应用相反,Tomcat 中的部署依赖于 Tomcat 服务器,以最大限度地减少 Web 应用升级的停机时间。

3、并行部署,实现不停机升级应用

在 Apache Tomcat 7 之前,重新部署正在运行的 Web 应用是很麻烦的。具体来说,需要重新启动 Tomcat 服务器才能重新部署现有的、正在运行的 Web 应用。对于当代应用来说,这是不可取的,因为在重新部署 Web 应用时会造成停机。此外,重新启动还会干扰在同一 Tomcat 实例中运行的其他 Web 应用。

幸运的是,Apache Tomcat 7 开始引入了 并行部署(Parallel Deployment) 机制,以支持 Web 应用不停机重新部署。

3.1、版本化部署

Apache Tomcat 的并行部署功能允许我们在不停机的情况下重新部署 Web 应用。

使用双 HASH 运算符 (##) 对部署进行版本控制,以实现并行部署。具体来说,需要在 WAR 文件后缀名前加上 ##{version},从而对 WAR 文件进行版本控制。

例如,将 demo.war 文件部署版本改为版本 1

$ mv demo.war demo##1.war

文件名中的版本信息不会影响 Tomcat 服务 Web 应用的上下文路径(Context Path)。在上例中,demo##1.war 仍部署在 /demo 上下文路径下提供服务。

随后,当要升级 Web 应用时,就要部署另一个版本号不同的 WAR 文件,如 demo##2.war

3.2、优雅升级

通过版本化部署,同一 Web 应用的任何后续部署,只要版本号不同,就会触发并行部署。具体来说,Tomcat 会启动新版本的 Web 应用。然后,它会逐渐将流量路由到与最新版本相同的上下文路径。重要的是,在此过程中,它仍会运行旧版本。这样可以确保旧版本提供的任何现有流量不会中断。

最终,所有流量都将被路由到新版本的 Web 应用。到那时,就可以停用旧的部署了。

4、并行部署演示

要了解 Apache Tomcat 的并行部署功能,首先要建立一个 Tomcat Web 服务器。然后,部署 Web 应用的第一个版本 web-v1.war。随后,使用 web-v2.war 文件执行升级,此时可以看到两个 Web 应用同时运行。

4.1、安装和运行 Apache Tomcat 服务器

首先,需要安装 Tomcat 软件。从其 官方网站 下载 Apache Tomcat 10 压缩包,然后将整个 ZIP 文件解压缩到工作目录中进行安装:

$ wget -qO apache-tomcat-10.zip https://dlcdn.apache.org/tomcat/tomcat-10/v10.1.24/bin/apache-tomcat-10.1.24.zip
$ unzip apache-tomcat-10.zip

然后,可以运行 bin/catalina.sh 脚本,通过 start 命令来启动 Tomcat 服务器:

$ ./apache-tomcat-10.1.24/bin/catalina.sh start
Using CATALINA_BASE:   /opt/tomcat-10/apache-tomcat-10.1.24
...
Tomcat started.

此时,Tomcat 服务器将启动并运行。我们可以通过向 localhost8080 端口发起 HTTP GET 请求来验证,看看是否能返回默认的 Tomcat 欢迎页面:

$ curl http://localhost:8080

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Apache Tomcat/10.1.24</title>
        ...

4.2、构建示例 Web 应用

为了便于演示,我们使用同一 Web 应用的两个不同版本:demo-v1.wardemo-v2.war。这是一个 Spring Boot 应用,在 /home 路由提供了一个 GET 端点。端点会返回一条文本信息:Hello world - version N,其中 N 对应版本号。这能直观地了解正在与之交互的 web 应用的版本。

这里不会深入讲解如何把 Spring Boot 构建为 WAR 包,并部署到外部 Tomcat 服务器中,具体细节你可以参阅 “将Spring Boot 应用构建为 War 包” 这篇文章。

4.3、首次部署

部署 Web 应用,并将第一次部署的版本定为版本 1:

$ cp target/demo-v1.war /opt/apache-tomcat-10.1.24/webapps/demo##1.war

上述命令将 WAR 文件复制到 Tomcat 实例的 webapps 目录来部署 Web 应用 demo-v1.war。重点在于把 WAR 文件重命名为 demo##1,以便对其进行版本控制。

我们可以使用 cURL 工具向 /home 路由发起 GET 请求来验证是否部署成功:

$ curl http://localhost:8080/demo/home
Hello world - version 1

4.4、升级应用

通过将 demo-v2.war 文件部署到 Tomcat Web 服务器来升级我们的 Web 应用。

使用相同的 cp 命令将文件复制到 webapps 目录,并在文件名中添加版本信息:

$ cp target/demo-v2.war /opt/apache-tomcat-10.1.24/webapps/demo##2.war

接着,立即多次运行 curl 命令,可以发现服务器会在升级过程中继续响应对 GET /home 的请求,旧版本的服务仍然在运行:

$ curl http://localhost:8080/demo/home
Hello world - version 1
$ curl http://localhost:8080/demo/home
Hello world - version 1
...

稍等片刻,就可以看到新部署的应用已经启动成功,而且请求已经被路由到新部署的应用中:

$ curl http://localhost:8080/demo/home
Hello world - version 2

5、总结

本文介绍了 Tomcat 的两种部署模式,以及如何在外部独立运行的 Tomcat 服务器中,通过其 “并行部署(Parallel Deployment)” 功能来实现应用的不停机升级。


Ref:https://www.baeldung.com/tomcat-zero-downtime-web-app-upgrade-parallel-deployment