tomcat-以最少的停机时间部署Java Web应用程序的最佳实践?

部署大型Java Web应用程序(> 100 MB .war)时,当前正在使用以下部署过程:

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

关于这种方法的好处:

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

这种方法的坏事:

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

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

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

题:

  • 给定所述要求,最佳部署过程是什么?
knorv asked 2019-11-19T01:57:48Z
18个解决方案
30 votes

更新:

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

原答案:

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

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

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

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

Asaph answered 2019-11-19T01:59:34Z
20 votes

已经注意到,将更改推送到WAR文件时,rsync不能很好地工作。 原因是WAR文件本质上是ZIP文件,并且默认情况下是使用压缩成员文件创建的。 对成员文件的较小更改(在压缩之前)会导致ZIP文件的比例差异很大,从而使rsync的增量传输算法无效。

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

编辑:根据WAR文件的结构,可能还需要使用common/lib创建组件JAR文件。 这将适用于经常更改(或简单地重建)的JAR文件,而不适用于稳定的第三方JAR文件。

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

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


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

Stephen C answered 2019-11-19T01:58:35Z
13 votes

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

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

如果我担心跨交换机保留会话数据,则可以设置系统,使每个主机都有一个向其复制会话数据的伙伴。 我可以删除其中一台主机,对其进行更新,将其备份,以便它选择备份会话数据,然后切换两台主机。 如果集群中有多个对,则可以删除所有对中的一半,然后进行批量切换,或者一次可以对它们进行一对,具体取决于发布的要求,企业的要求等。 但是,就我个人而言,我更愿意让最终用户偶尔遭受活动会话的损失,而不是尝试在会话保持不变的情况下进行升级。

这都是IT基础架构,发布流程的复杂性和开发人员的努力之间的权衡。如果您的集群足够大,并且您的需求足够强烈,那么设计一个可以轻松替换大多数更新的系统就很容易了。较大的架构更改通常会迫使实际的停机时间,因为更新的软件通常无法容纳旧的架构,并且您可能无法摆脱将数据复制到新的数据库实例,进行架构更新,然后将服务器切换到新数据库的麻烦,因为从新数据库克隆后,您将丢失任何写入旧数据的数据。当然,如果您有资源,则可以让开发人员修改新的应用程序,以对所有已更新的表使用新的表名,并且可以将触发器放置在实时数据库中,这将正确地使用数据更新新表。它是由先前版本写入旧表的(或者可以使用视图从另一个模式模仿一个模式)。启动新的应用程序服务器,并将它们交换到群集中。如果您有开发资源,可以玩很多游戏,以最大程度地减少停机时间。

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

ideasculptor answered 2019-11-19T02:00:32Z
10 votes

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

  1. 在实时环境中创建临时文件夹,在该环境中将爆炸Webapp版本。
  2. Rsync爆炸版本。
  3. 成功rsync后,在实时环境计算机的临时文件夹中创建一个war文件。
  4. 用临时文件夹中的新目录替换服务器部署目录中的旧目录。

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

cetnar answered 2019-11-19T02:01:31Z
8 votes

您是否不能在Web服务器上创建当前Web应用程序的本地副本,将其同步到该目录,然后甚至可以使用符号链接一次“转到”,从而将Tomcat指向新的部署而又不会造成大量停机?

jarnbjo answered 2019-11-19T02:01:57Z
4 votes

rsync提取战争的方法非常好,而且重启也可以,因为我认为生产服务器不应启用热部署。 因此,唯一的缺点是需要重新启动服务器时的停机时间,对吗?

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

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

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

mhaller answered 2019-11-19T02:02:43Z
4 votes

这取决于您的应用程序体系结构。

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

Matt answered 2019-11-19T02:03:15Z
4 votes

热部署Java EAR以最大程度地减少或消除服务器上应用程序的停机时间,或者如何使用Jboss Tools Eclipse插件在Jboss中“热”部署战争依赖项。

在没有停机的情况下部署到集群也很有趣。

JavaRebel也具有热代码部署。

elhoim answered 2019-11-19T02:03:53Z
2 votes

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

因此,与其产生大量的脂肪战争,不如将其用于饮食并产生:

  • 一个带有Apache静态文件的大胖ZIP
  • Servlet引擎的WAR更少。

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

如果您成功产生了一个轻量级的WAR,那么我将不费心地同步目录而不是归档。

这种方法的优点:

  1. 静态文件可以在Apache上进行热“部署”(例如,使用指向当前目录的符号链接,解压缩新文件,更新符号链接并修改)。
  2. WAR会更薄,并且部署时间会更少。

这种方法的缺点:

  1. 还有一台服务器(Web服务器),因此增加了(更多)复杂性。
  2. 您需要更改构建脚本(不是很大的IMO)。
  3. 您需要更改rsync逻辑。
Pascal Thivent answered 2019-11-19T02:05:40Z
1 votes

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

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

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

如果通常要考虑停机时间,那么即使我的项目没有共享状态,而是使用提供粘性会话路由的路由器,它们通常也是HA。

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

我希望这是有帮助的。

Kent Lai answered 2019-11-19T02:07:00Z
1 votes

您的PermSpace设置在什么位置? 我希望这种情况也会增长,但是在收集完旧课程之后应该下降吗? (或者ClassLoader还是坐在那里吗?)

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

Jé Queue answered 2019-11-19T02:07:33Z
1 votes

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

toomasr answered 2019-11-19T02:07:58Z
1 votes

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

我们会在生产服务器上禁用自动重装以提高性能,但是即使这样,以非原子方式更改webapp中的文件也会引起问题,因为静态文件甚至JSP页面可能以导致链接断开或更糟的方式进行更改。

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

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

Kief answered 2019-11-19T02:08:45Z
1 votes

Tomcat 7具有一个很好的功能,称为“并行部署”,它是为此用例设计的。

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

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

craigforster answered 2019-11-19T02:09:25Z
1 votes

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

现在,在每个代理服务器中,都配置了带有端口的“输入”和“输出” URL。

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

请注意在server.xml中的节点“主机”中交叉检查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]以获取映射网址

ravi ranjan answered 2019-11-19T02:10:49Z
1 votes

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

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

更新:它的监视程序也可以帮助解决permgenspace问题。

danieljimenez answered 2019-11-19T02:11:29Z
0 votes

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

如何通过git等DVCS部署Web应用程序?

这样,您可以让git找出哪些文件要传输到服务器。 如果发现它已损坏,您还可以使用一种不错的方法来退出它,只需执行还原即可!

John Nilsson answered 2019-11-19T02:12:09Z
0 votes

我编写了一个bash脚本,该脚本带有一些参数并使服务器之间的文件同步。 大型档案可以大大加快rsync传输:

[https://gist.github.com/3985742]

Kehan answered 2019-11-19T02:12:41Z
translate from https://stackoverflow.com:/questions/1640333/best-practices-for-deploying-java-webapps-with-minimal-downtime