用最less的停机时间部署Java webapps的最佳实践?

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

  • 应用程序.war文件在开发机器上进行本地扩展。
  • 扩展的应用程序是从开发机器到现场环境的rsync:ed。
  • 实时环境中的应用程序服务器在rsync之后重新启动。 这一步不是严格需要的,但是我发现在部署时重新启动应用程序服务器避免了由于频繁的类加载导致的“java.lang.OutOfMemoryError:PermGen空间”。

这个方法的好处是:

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

这种方法不好的地方:

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

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

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

题:

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

已经注意到,在将更改推送到WAR文件时,rsync不能正常工作。 这是因为WAR文件本质上是ZIP文件,默认情况下是使用压缩的成员文件创build的。 成员文件(压缩之前)的小改动导致ZIP文件的大规模差异,使得rsync的增量转换algorithm无效。

一个可能的解决scheme是使用jar -0 ...来创build原始的WAR文件。 -0选项告诉jar命令在创buildWAR文件时不压缩成员文件。 然后,当rsync比较WAR文件的旧版本和新版本时,增量转移algorithm应该能够创build小差异。 然后安排rsync以压缩格式发送差异(或原始文件); 例如使用rsync -z ...或下面的压缩数据stream/传输。

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

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

缺点是部署的WAR文件会更大。 这可能会导致更长的webapp启动时间,但我怀疑效果是微不足道的。


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

更新:

由于这个答案是第一次写的,一个更好的方式部署战争文件到tomcat零宕机已经出现。 在最近版本的tomcat中,你可以在你的war文件名中包含版本号。 因此,例如,您可以同时将文件ROOT##001.warROOT##002.war到相同的上下文中。 ##之后的所有内容都被tomcat解释为版本号,而不是上下文path的一部分。 Tomcat将保持您的应用程序的所有版本运行,并将新的请求和会话提供给最新版本,同时正常地完成旧版请求和会话。 指定版本号也可以通过tomcatpipe理器甚至catalina ant任务完成。 更多信息在这里 。

原始答案:

Rsync对于压缩文件往往是无效的,因为它是增量传输algorithm查找文件中的变化和小改变未压缩的文件,可以大大改变最终的压缩版本。 出于这个原因,如果networking带宽被certificate是一个瓶颈,那么rsync是一个非压缩war文件而不是压缩版本。

使用Tomcatpipe理器应用程序执行部署有什么问题? 如果您不希望将整个war文件直接从远程位置上传到Tomcatpipe理器应用程序,则可以将其(未按照上述原因压缩)rsync同步到生产箱上的占位符位置,重新打包为一场战争,然后把它交给经理本地。 Tomcat附带的一个很好的ant任务允许您使用Tomcatpipe理器应用程序来部署脚本。

在你的方法中还有一个你没有提到的漏洞:当你的应用程序部分部署时(在一个rsync操作中),你的应用程序可能处于一个不一致的状态,在这个状态下更改的接口可能不同步,新的/更新的依赖项可能不可用等。另外,根据您的rsync作业需要多长时间,您的应用程序实际上可能会重新启动多次。 您是否意识到您可以并应该closuresTomcat中的监听更改文件和重新启动行为? 实际上不推荐用于生产系统。 您始终可以使用Tomcatpipe理器应用程序手动或通过脚本重新启动应用程序。

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

在任何停机时间考虑的环境中,您肯定会运行某种服务器集群以通过冗余提高可靠性。 我会把主机从集群中取出,更新它,然后把它放回到集群中。 如果您的更新无法在混合环境中运行(例如,数据库需要进行不兼容的模式更改),则至less需要暂时停止整个站点。 诀窍是在丢弃原稿之前调出replace过程。

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

如果我担心在交换机上保留会话数据,我可以设置我的系统,使每个主机都有一个合作伙伴,它可以复制会话数据。 我可以放弃其中一台主机,进行更新,将其备份,以便备份会话数据,然后切换两台主机。 如果我在群集中有多对,我可以放下所有对中的一半,然后进行批量交换,或者根据发布的要求,企业的要求等,我可以一次完成一对。 。但是,我个人更喜欢只允许最终用户偶尔损失一个活动会话,而不是处理完整的会话升级。

这完全是IT基础设施,发布stream程复杂性和开发人员努力之间的折衷。 如果你的集群足够大,你的愿望足够强大,那么很容易devise一个系统,在没有停机的情况下可以换出大部分更新。 大的模式更改通常会导致实际的停机时间,因为更新的软件通常无法容纳旧的模式,并且您可能无法避免将数据复制到新的数据库实例,进行模式更新,然后将服务器切换到新的数据库,因为你会错过任何数据写入旧的新的数据库克隆它。 当然,如果你有资源,你可以让开发人员修改新的应用程序,为所有更新的表使用新的表名,并且可以在活动分区上放置触发器,这些触发器将会正确地更新新的表,其中数据为它是由旧版本写入旧表(或者可以使用视图来模拟另一个模式)。 调出新的应用程序服务器并将其交换到群集中。 如果你有开发资源来build立它们,你可以玩很多的游戏来减less停机时间。

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

我的build议是使用分解版本的rsync,但部署一个战争文件。

  1. 在实际环境中创build临时文件夹,在该环境中,您将展开Web应用程序的版本。
  2. Rsync爆炸版本。
  3. 在成功的rsync之后,在live环境机器的临时文件夹中创build一个war文件。
  4. 将服务器部署目录中的旧warreplace为临时文件夹中的新war。

在JBoss容器(基于Tomcat)中推荐使用新的替代旧的战争,因为它是一个primefaces性和快速的操作,并且当部署者启动整个应用程序的时候,它将会处于部署状态。

难道你不能在Web服务器上创build当前web应用程序的本地副本rsync到那个目录,然后甚至可以使用符号链接,在一个“去”中,指向一个新的部署没有太多停机时间的Tomcat?

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

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

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

如果您可以使用应用程序服务器的群集和会话复制支持,那么当前login的用户将会更加顺利,因为第二个应用程序服务器将在启动后立即重新同步。 然后,当没有访问第一台服务器时,closures它。

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

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

热部署Java EAR以最小化或消除服务器上应用程序的停机时间或者如何使用Jboss工具“热”部署Jboss中的战争依赖关系Eclipse插件可能为您提供了一些选项。

部署到没有停机时间的集群也很有趣。

JavaRebel也有热代码部署 。

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

所以,不要制造一个大胖子的战争,而应该饮食和生产:

  • 一个带有Apache静态文件的大型ZIP压缩文件
  • 这个servlet引擎的WAR较less。

可选地,继续进行WAR稀释程序:如果可能的话,在应用程序服务器级别部署不经常更改的Grails和其他JAR(这可能是大多数情况)。

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

这种方法的优点:

  1. 静态文件可以在Apache上进行热部署(例如,使用指向当前目录的符号链接,解压缩新文件,更新符号链接和声音)。
  2. 战争将变得更加薄弱,部署时间也会缩短。

这种方法的弱点:

  1. 还有一个服务器(networking服务器),所以这增加了(一点)更复杂。
  2. 您将需要更改构build脚本(不是一个大问题IMO)。
  3. 您将需要更改rsync逻辑。

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

与你相似,我永远不会记得全面的战争重新部署或更新。 大多数情况下,我的更新仅限于几个jsp文件,也许是一个库,一些类文件。 我能够pipe理和确定哪些是受影响的工件,通常,我们将这些更新打包在一个zip文件中,以及一个更新脚本。 我将运行更新脚本。 该脚本执行以下操作:

  • 备份将被覆盖的文件,可能是当前date和时间的文件夹。
  • 解压我的文件
  • 停止应用程序服务器
  • 移动文件
  • 启动应用程序服务器

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

另一件我很好奇的事情是,为什么需要rsync? 您应该能够通过在分段/开发环境中确定需要的更改,而不是通过实时执行delta检查。 在大多数情况下,你将不得不调整你的rsync来忽略文件,比如某些定义生产服务器使用的资源的属性文件,比如数据库连接,smtp服务器等等。

我希望这是有帮助的。

什么是您的PermSpace设置? 我希望看到这个增长,但收集旧的class后应该下降? (或者ClassLoader还在吗?)

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

至于早期的上下文重启。 所有容器都有configuration选项来禁用对类文件的自动重新部署或静态资源更改。 您可能无法禁用对web.xml更改的自动重新部署,所以这个文件是最后一个更新。 所以如果你禁用自动重新部署和更新web.xml作为最后一个,你会看到整个更新的上下文重新启动。

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

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

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

你的情况的主要缺点是,上传将花费更长时间,因为你的方法允许rsync只传输修改或添加的文件。 你可以先将旧的webapp文件夹复制到新的文件夹中,然后将rsync复制到新的文件夹中,如果它有重大的不同,以及是否真的有问题。

Tomcat 7有一个很好的function,称为“ 并行部署 ”,是专为这个用例。

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

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

只要使用2个或更多的tomcat服务器和代理就可以了。 该代理可以是apache / nignix / haproxy。

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

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

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

它看起来很喜欢这个

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

现在看到tomcat的日志。 如果没有错误,那就意味着它成功了。

现在打所有的API进行testing

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

只需更改新的战争名称的背景url映射。 由于使用像apache / nignix / haProxy这样的代理服务器进行注册需要很less的时间,所以您会觉得停机时间最短

请参阅 – https://developers.google.com/speed/pagespeed/module/domains来映射url

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

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

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

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

如何通过DVCS如git部署webapp?

这样你可以让git弄清楚哪些文件要传输到服务器。 你也有一个很好的方法来退出它,如果事实certificate是捣毁,只是做一个恢复!

我写了一个bash脚本,它需要几个参数,并在服务器之间rsyncs文件。 加速rsync传输大量档案:

https://gist.github.com/3985742

Interesting Posts