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

时间:2009-10-28 21:46:39

标签: java tomcat grails deployment resin

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

  • 应用程序.war文件在开发计算机上本地扩展。
  • 扩展的应用程序是rsync:从开发机器到实时环境。
  • 在rsync之后重新启动实时环境中的应用服务器。这个步骤并不是严格需要的,但我发现在部署时重新启动应用程序服务器可以避免因频繁的类加载而导致“java.lang.OutOfMemoryError:PermGen space”。

这种方法的好处:

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

关于这种方法的坏事:

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

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

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

问题:

  • 根据规定的要求,最佳部署流程是什么?

18 个答案:

答案 0 :(得分:30)

更新

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

原始答案:

Rsync往往对压缩文件无效,因为它的delta传输算法会查找文件中的更改,并且对未压缩文件进行少量更改,可能会彻底改变生成的压缩版本。因此,如果网络带宽被证明是一个瓶颈,那么rsync一个未压缩的war文件而不是压缩版本可能是很有意义的。

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

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

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

答案 1 :(得分:20)

已经注意到,当将更改推送到WAR文件时,rsync不能正常工作。原因是WAR文件本质上是ZIP文件,默认情况下是使用压缩成员文件创建的。成员文件的小变化(压缩前)会导致ZIP文件出现大规模差异,导致rsync的delta传输算法失效。

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

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

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

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


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

答案 2 :(得分:13)

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

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

如果我担心在交换机上保留会话数据,我可以设置我的系统,使每个主机都有一个合作伙伴,它可以复制会话数据。我可以删除其中一个主机,更新它,重新启动它以便它选择备份会话数据,然后切换两个主机。如果我在群集中有多对,我可以丢弃所有对中的一半,然后进行批量切换,或者我可以一次执行一对,具体取决于发布的要求,企业的要求等但就个人而言,我更愿意让最终用户偶尔失去一个活跃的会话,而不是试图通过完整的会话进行升级。

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

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

答案 3 :(得分:10)

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

  1. 在实时环境中创建临时文件夹,您将拥有webapp的爆炸版本。
  2. Rsync爆炸版本。
  3. 成功后,rsync在实时环境机器的临时文件夹中创建一个war文件。
  4. 使用临时文件夹中的新战争替换服务器部署目录中的旧战争。
  5. 建议在JBoss容器(基于Tomcat)中替换旧的战争,因为它是原子级的快速操作,并确保当部署者启动整个应用程序时将处于部署状态。

答案 4 :(得分:8)

你不能在Web服务器上创建当前Web应用程序的本地副本,rsync到该目录,然后甚至可能在一个“go”中使用符号链接,将Tomcat指向一个没有太多停机时间的新部署吗? / p>

答案 5 :(得分:4)

答案 6 :(得分:4)

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

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

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

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

答案 7 :(得分:4)

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

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

答案 8 :(得分:2)

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

所以,不要生产大胖的WAR,而是将它放在节食和生产上:

  • 带有Apache的静态文件的大胖ZIP
  • servlet引擎的低脂肪WAR。

(可选)进一步完成使WAR更薄的过程:如果可能,在应用程序服务器级别部署Grails和其他不经常更改的JAR(大多数情况可能就是这种情况)。

如果你成功生成了一个更轻的WAR,我就不会打扰rsyncing目录而不是档案。

这种方法的优点:

  1. 静态文件可以在Apache上“部署”(例如,使用指向当前目录的符号链接,解压缩新文件,更新符号链接和voilà)。
  2. WAR将更薄,部署它所需的时间更短。
  3. 这种做法的弱点:

    1. 还有一台服务器(Web服务器),因此这会增加(稍微)一点复杂性。
    2. 您需要更改构建脚本(不是什么大不了的IMO)。
    3. 您需要更改rsync逻辑。

答案 9 :(得分:1)

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

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

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

答案 10 :(得分:1)

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

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

首先在tomcat中复制你的战争而不停止服务。一旦部署了war,它就会被tomcat引擎自动打开。

注意交叉检查unpackWARs =" true"和autoDeploy =" true"在节点"主机"在server.xml内部

看起来像这样

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

现在看看tomcat的日志。如果没有错误,则意味着它已成功启动。

现在点击所有API进行测试

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

只需使用新战争的名称更改背景网址映射即可。由于使用apache / nignix / haProxy等代理服务器注册的时间非常短,因此您可以获得最短的停机时间

参考 - https://developers.google.com/speed/pagespeed/module/domains了解地图网址

答案 11 :(得分:1)

Tomcat 7有一个很好的功能,名为“parallel deployment”,专为此用例而设计。

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

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

答案 12 :(得分:1)

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

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

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

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

答案 13 :(得分:1)

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

答案 14 :(得分:1)

您的PermSpace设置是什么?我希望看到这种情况也会增长,但应该在收集旧课程后失败吗? (或ClassLoader是否仍然坐着?)

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

答案 15 :(得分:1)

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

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

  • 将要覆盖的文件备份到可能包含今天日期和时间的文件夹中。
  • 解压缩我的文件
  • 停止应用程序服务器
  • 移动文件
  • 启动应用程序服务器

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

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

我希望这有用。

答案 16 :(得分:0)

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

如何通过诸如git之类的DVCS部署webapp?

这样你可以让git找出要传输到服务器的文件。你还有一个很好的方法可以退出它,如果事实证明它被破坏了,只需要恢复!

答案 17 :(得分:0)

我编写了一个bash脚本,它接受一些参数并在服务器之间rsyncs文件。为更大的档案加速rsync传输:

https://gist.github.com/3985742