58

在部署大型 Java webapp (>100 MB .war) 时,我目前使用以下部署过程:

  • 应用程序 .war 文件在开发机器上本地展开。
  • 扩展的应用程序从开发机器rsync:ed到实时环境。
  • 实时环境中的应用服务器在 rsync 之后重新启动。这一步并不是必须的,但我发现在部署时重新启动应用程序服务器可以避免由于频繁加载类而出现“java.lang.OutOfMemoryError: PermGen space”。

这种方法的好处:

  • rsync 最大限度地减少了从开发机器发送到实时环境的数据量。上传整个 .war 文件需要十多分钟,而 rsync 需要几秒钟。

这种方法的坏处:

  • 当 rsync 运行时,应用程序上下文会重新启动,因为文件已更新。理想情况下,应该在 rsync 完成后重新启动,而不是在它仍在运行时。
  • 应用服务器重新启动会导致大约两分钟的停机时间。

我想找到具有以下属性的部署过程:

  • 部署过程中的停机时间最短。
  • 上传数据所花费的时间最少。
  • 如果部署过程是特定于应用服务器的,那么应用服务器必须是开源的。

问题:

  • 鉴于所述要求,最佳部署流程是什么?
4

18 回答 18

31

更新:

自从首次编写此答案以来,出现了一种将战争文件部署到 tomcat 且停机时间为零的更好方法。在最新版本的 tomcat 中,您可以在您的 war 文件名中包含版本号。因此,例如,您可以同时将文件部署ROOT##001.warROOT##002.war相同的上下文中。之后的所有内容都##被tomcat解释为版本号,而不是上下文路径的一部分。Tomcat 将使您的应用程序的所有版本保持运行,并为完全启动的最新版本提供新的请求和会话,同时在它们开始的版本上优雅地完成旧请求和会话。指定版本号也可以通过 tomcat 管理器甚至 catalina ant 任务来完成。更多信息在这里

原答案:

Rsync 对压缩文件往往无效,因为它的增量传输算法会查找文件中的更改,而未压缩文件的微小更改可能会极大地改变生成的压缩版本。出于这个原因,如果网络带宽被证明是一个瓶颈,那么对未压缩的 war 文件而不是压缩版本进行 rsync 可能更有意义。

使用 Tomcat 管理器应用程序进行部署有什么问题?如果您不想将整个 war 文件从远程位置直接上传到 Tomcat 管理器应用程序,您可以将其 rsync(由于上述原因未压缩)到生产盒上的占位符位置,将其重新打包到 war 中,然后然后交给当地的经理。Tomcat 附带了一个不错的 ant 任务,允许您使用 Tomcat 管理器应用程序编写部署脚本。

您的方法中还有一个您没有提到的额外缺陷:当您的应用程序部分部署时(在 rsync 操作期间),您的应用程序可能处于不一致的状态,其中更改的接口可能不同步,新的/更新的依赖项可能不可用等。此外,根据您的 rsync 作业需要多长时间,您的应用程序实际上可能会重新启动多次。您是否知道您可以并且应该关闭 Tomcat 中的监听更改文件和重新启动行为?实际上不推荐用于生产系统。您始终可以使用 Tomcat 管理器应用程序手动或 ant 脚本重新启动您的应用程序。

当然,您的应用程序将在重新启动期间对用户不可用。但是,如果您非常关心可用性,那么您肯定在负载均衡器后面有冗余的 Web 服务器。部署更新的 war 文件时,您可以暂时让负载均衡器将所有请求发送到其他 Web 服务器,直到部署结束。冲洗并重复您的其他 Web 服务器。

于 2009-10-28T21:56:20.480 回答
20

已经注意到,在将更改推送到 WAR 文件时,rsync 无法正常工作。原因是 WAR 文件本质上是 ZIP 文件,默认情况下是使用压缩的成员文件创建的。对成员文件的小改动(压缩前)会导致 ZIP 文件的大规模差异,从而导致 rsync 的 delta-transfer 算法无效。

一种可能的解决方案是使用jar -0 ...创建原始 WAR 文件。该-0选项告诉jar命令在创建 WAR 文件时不要压缩成员文件。然后,当rsync比较 WAR 文件的新旧版本时,delta-transfer 算法应该能够创建小的差异。然后安排 rsync 以压缩形式发送差异(或原始文件);例如使用rsync -z ...或压缩数据流/传输。

编辑:根据 WAR 文件的结构,可能还需要用于jar -0 ...创建组件 JAR 文件。这将适用于经常更改(或只是重新构建)的 JAR 文件,而不适用于稳定的第 3 方 JAR 文件。

从理论上讲,这个过程应该比发送常规 WAR 文件有显着的改进。在实践中我没有尝试过,所以我不能保证它会起作用。

缺点是部署的 WAR 文件要大得多。这可能会导致更长的 webapp 启动时间,尽管我怀疑这种影响是微不足道的。


完全不同的方法是查看您的 WAR 文件,看看您是否可以识别可能(几乎)永远不会更改的库 JAR。将这些 JAR 从 WAR 文件中取出,分别部署到 Tomcat 服务器的common/lib目录中;例如使用rsync.

于 2009-10-29T05:43:08.417 回答
13

在考虑停机时间的任何环境中,您肯定会运行某种服务器集群以通过冗余来提高可靠性。我会从集群中取出一台主机,对其进行更新,然后将其放回集群中。如果您有一个无法在混合环境中运行的更新(例如,数据库上需要不兼容的架构更改),您将不得不关闭整个站点,至少暂时关闭。诀窍是在丢弃原件之前启动替换过程。

以 tomcat 为例 - 您可以使用 CATALINA_BASE 定义一个目录,在该目录中可以找到所有 tomcat 的工作目录,与可执行代码分开。每次我部署软件时,我都会部署到一个新的基本目录,这样我就可以将新代码驻留在磁盘上,与旧代码相邻。然后,我可以启动另一个指向新基目录的 tomcat 实例,启动并运行所有内容,然后将旧进程(端口号)与负载平衡器中的新进程交换。

如果我担心跨交换机保留会话数据,我可以设置我的系统,使每个主机都有一个合作伙伴,它将会话数据复制到该合作伙伴。我可以删除其中一台主机,对其进行更新,将其重新启动,以便它恢复会话数据,然后切换两台主机。如果集群中有多个pair,我可以丢弃一半的pair,然后进行批量切换,或者我可以一次做一对,具体取决于发布的要求、企业的要求等. 然而,就个人而言,我更愿意让最终用户遭受非常偶然的活动会话丢失,而不是尝试在会话完好无损的情况下进行升级。

这完全是 IT 基础架构、发布过程复杂性和开发人员工作量之间的权衡。如果您的集群足够大并且您的愿望足够强烈,那么设计一个系统可以很容易地进行更换,而无需停机进行大多数更新。大型架构更改通常会导致实际停机,因为更新的软件通常无法适应旧架构,并且您可能无法将数据复制到新的数据库实例,进行架构更新,然后将服务器切换到新数据库,因为从新数据库克隆后,您将错过写入旧数据库的任何数据。当然,如果您有资源,您可以要求开发人员修改新应用程序以对所有更新的表使用新表名,并且您可以在实时数据库上放置触发器,这将使用数据正确更新新表,因为它是由先前版本写入旧表的(或者可能使用视图来模拟另一个模式)。启动您的新应用服务器并将它们交换到集群中。如果您有开发资源来构建它们,则可以玩大量游戏以最大程度地减少停机时间。

在软件升级期间减少停机时间的最有用的机制可能是确保您的应用程序可以在只读模式下运行。这将为您的用户提供一些必要的功能,但让您能够进行需要数据库修改等的系统范围的更改。将您的应用程序置于只读模式,然后克隆数据、更新架构、针对新数据库启动新的应用程序服务器,然后切换负载均衡器以使用新的应用程序服务器。您唯一的停机时间是切换到只读模式所需的时间和修改负载均衡器配置所需的时间(其中大部分可以在没有任何停机时间的情况下处理它)。

于 2009-11-05T19:30:18.830 回答
10

我的建议是将 rsync 与爆炸版本一起使用,但部署一个 war 文件。

  1. 在您将拥有 webapp 爆炸版本的实时环境中创建临时文件夹。
  2. Rsync 爆炸版本。
  3. 成功 rsync 后,在实时环境机器的临时文件夹中创建一个 war 文件。
  4. 将服务器部署目录中的旧战争替换为临时文件夹中的新战争。

建议在 JBoss 容器(基于 Tomcat)中用新的战争替换旧战争,因为它是一种原子且快速的操作,并且可以确定当部署者启动时,整个应用程序将处于部署状态。

于 2009-11-01T20:13:40.947 回答
8

您不能在 Web 服务器上制作当前 Web 应用程序的本地副本,rsync 到该目录,然后甚至可能使用符号链接,一次“开始”,将 Tomcat 指向一个新的部署而不需要太多停机时间吗?

于 2009-10-28T23:01:25.497 回答
4

您对提取的战争进行 rsync 的方法非常好,重启也是因为我相信生产服务器不应该启用热部署。所以,唯一的缺点是需要重新启动服务器时的停机时间,对吧?

我假设您的应用程序的所有状态都保存在数据库中,因此您对某些用户在一个应用程序服务器实例上工作而其他用户在另一个应用程序服务器实例上没有问题。如果是这样的话,

运行两个应用服务器:启动第二个应用服务器(侦听其他 TCP 端口)并在那里部署您的应用程序。部署后,更新 Apache httpd 的配置(mod_jk 或 mod_proxy)以指向第二个应用服务器。优雅地重新启动 Apache httpd 进程。这样您就不会停机,并且新用户和请求会自动重定向到新的应用服务器。

如果您可以利用应用服务器的集群和会话复制支持,那么对于当前登录的用户来说将更加流畅,因为第二个应用服务器将在启动后立即重新同步。然后,当没有访问第一台服务器时,将其关闭。

于 2009-11-04T13:46:20.013 回答
4

这取决于您的应用程序架构。

我的一个应用程序位于负载平衡代理后面,在那里我执行交错部署 - 有效地消除了停机时间。

于 2009-11-06T13:41:10.173 回答
2

如果静态文件是大型 WAR 的重要组成部分(100Mo 相当大),那么将它们放在 WAR 之外并将它们部署在应用程序服务器前面的 Web 服务器(例如 Apache)上可能会加快速度。最重要的是,Apache 在提供静态文件方面通常比 servlet 引擎做得更好(即使它们中的大多数在该领域取得了重大进展)。

因此,与其产生大肥战争,不如节食并产生:

  • 一个带有 Apache 静态文件的大 ZIP
  • servlet 引擎的 WAR 较少。

可选地,在使 WAR 更精简的过程中走得更远:如果可能,在应用程序服务器级别部署 Grails 和其他不经常更改的 JAR(可能是大多数情况下的情况)。

如果你成功地制作了一个更轻的 WAR,我不会费心 rsyncing 目录而不是档案。

这种方法的优点:

  1. 静态文件可以在 Apache 上热“部署”(例如,使用指向当前目录的符号链接,解压缩新文件,更新符号链接,然后瞧)。
  2. WAR 会更薄,部署它所需的时间也会更少。

这种方法的缺点:

  1. 还有一个服务器(Web 服务器),所以这增加了(一点)复杂性。
  2. 您需要更改构建脚本(IMO 没什么大不了的)。
  3. 您需要更改 rsync 逻辑。
于 2009-11-04T21:45:00.043 回答
1

我不确定这是否回答了您的问题,但我将分享我在我所做的几个项目中使用或遇到的部署过程。

与您类似,我不记得进行过全面的战争重新部署或更新。大多数时候,我的更新仅限于几个 jsp 文件,可能是一个库,一些类文件。我能够管理和确定哪些是受影响的工件,通常,我们将这些更新与更新脚本一起打包在一个 zip 文件中。我将运行更新脚本。该脚本执行以下操作:

  • 将将被覆盖的文件备份到具有今天日期和时间的文件夹中。
  • 解压缩我的文件
  • 停止应用服务器
  • 移动文件
  • 启动应用服务器

如果停机时间是一个问题,而且通常是,我的项目通常是 HA,即使它们不是共享状态而是使用提供粘性会话路由的路由器。

我很好奇的另一件事是,为什么需要 rsync?您应该能够通过在暂存/开发环境中确定它们来了解所需的更改,而不是实时执行增量检查。在大多数情况下,您必须调整 rsync 以忽略文件,例如定义生产服务器使用的资源的某些属性文件,例如数据库连接、smtp 服务器等。

我希望这是有帮助的。

于 2009-10-31T15:11:50.230 回答
1

你的 PermSpace 设置在什么位置?我希望看到它也会增长,但在收集旧课程后应该下降吗?(或者 ClassLoader 是否仍然存在?)

大声思考,您可以 rsync 到单独的以版本或日期命名的目录。如果容器支持符号链接,你可以 SIGSTOP 根进程,通过符号链接切换上下文的文件系统根,然后 SIGCONT?

于 2009-11-05T19:36:34.587 回答
1

至于早期的上下文重启。所有容器都有配置选项来禁用对类文件或静态资源更改的自动重新部署。您可能无法在 web.xml 更改时禁用自动重新部署,因此该文件是最后一个要更新的文件。因此,如果您禁用自动重新部署并将 web.xml 更新为最后一个,您将在整个更新后看到上下文重新启动。

于 2009-11-06T08:56:52.273 回答
1

我们将新版本的 webapp 上传到一个单独的目录,然后移动以将其与正在运行的版本交换,或者使用符号链接。例如,我们在 tomcat webapps 目录中有一个名为“myapp”的符号链接,它指向名为“myapp-1.23”的当前 webapp。我们将新的 webapp 上传到“myapp-1.24”。一切准备就绪后,停止服务器,删除符号链接并创建一个指向新版本的新链接,然后再次启动服务器。

我们在生产服务器上禁用自动重新加载以提高性能,但即便如此,以非原子方式更改 webapp 中的文件可能会导致问题,因为静态文件甚至 JSP 页面可能会以导致断开链接或更糟的方式更改。

实际上,Web 应用程序实际上位于共享存储设备上,因此集群、负载平衡和故障转移服务器都具有相同的可用代码。

您的情况的主要缺点是上传需要更长的时间,因为您的方法允许 rsync 仅传输修改或添加的文件。您可以先将旧的 webapp 文件夹复制到新的文件夹,然后 rsync 到那个文件夹,如果它有很大的不同,并且如果它真的是一个问题。

于 2010-04-30T12:16:35.560 回答
1

Tomcat 7 有一个很好的特性,称为“并行部署”,它是为这个用例设计的。

要点是您将 .war 展开到一个目录中,可以直接在 webapps/ 下或 symlinked 下。应用程序的后续版本位于名为 的目录app##version中,例如myapp##001myapp##002。Tomcat 将处理转到旧版本的现有会话,以及转到新版本的新会话。

问题是你必须非常小心 PermGen 泄漏。对于使用大量 PermGen 的 Grails 尤其如此。VisualVM 是您的朋友。

于 2013-11-02T16:58:48.757 回答
1

只需使用 2 个或更多带有代理的 tomcat 服务器。该代理可以是 apache/nignix/haproxy。

现在在每个代理服务器中都有配置端口的“in”和“out”url。

首先在不停止服务的情况下将您的战争复制到tomcat中。一旦war被部署,它就会被tomcat引擎自动打开。

注意在 server.xml 中的节点“Host”中交叉检查 unpackWARs="true" 和 autoDeploy="true"

看起来像这样

  <Host name="localhost"  appBase="webapps"
        unpackWARs="true" autoDeploy="true"
        xmlValidation="false" xmlNamespaceAware="false">

现在查看tomcat的日志。如果没有错误,则表示它已成功启动。

现在点击所有 API 进行测试

现在来到您的代理服务器。

只需使用新战争的名称更改背景 url 映射。由于在 apache/nignix/haProxy 等代理服务器上注册花费的时间非常少,因此您会感到停机时间最短

参考 - https://developers.google.com/speed/pagespeed/module/domains映射 url

于 2014-04-19T16:24:59.213 回答
1

您正在使用 Resin,Resin 内置了对 Web 应用程序版本控制的支持。

http://www.caucho.com/resin-4.0/admin/deploy.xtp#VersioningandGracefulUpgrades

更新:它的看门狗进程也可以帮助解决 permgenspace 问题。

于 2014-08-01T00:35:08.297 回答
0

不是“最佳实践”,而是我刚刚想到的。

通过 DVCS(如 git)部署 webapp 怎么样?

这样你就可以让 git 找出要传输到服务器的文件。如果它被破坏,你也有一个很好的方法来退出它,只是做一个还原!

于 2010-05-21T14:42:13.667 回答
0

我编写了一个 bash 脚本,它接受一些参数并在服务器之间同步文件。对于较大的档案,大大加快了 rsync 传输:

https://gist.github.com/3985742

于 2012-10-31T08:11:30.210 回答