【问题标题】:Best practices for deploying Java webapps with minimal downtime?以最少的停机时间部署 Java webapps 的最佳实践?
【发布时间】:2010-12-11 01:15:06
【问题描述】:

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

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

这种方法的好处:

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

这种方法的坏处:

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

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

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

问题:

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

【问题讨论】:

  • 在我看来这应该是一个“社区维基”
  • 内森:为什么?这是一个我需要回答的技术问题。也许我错过了一些围绕“社区 wiki”的规则。
  • 只是为了满足我的好奇心:你的 webapp 有什么东西这么重?
  • Pascal Thivent:Grails + 静态文件(图形)+ 一些外部依赖项很快就会增加到 >100 MB。
  • knorv,您是否尝试过调整服务器 jvm 上的内存/permgem 空间?

标签: java tomcat grails deployment resin


【解决方案1】:

更新:

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

原答案:

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

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

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

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

【讨论】:

  • 据我了解,rsync:ing 两个相似目录的 zip 表示不会给我带来与 rsync:ing 这两个目录相同的速度优势。如果我错了,请纠正我。
  • 问题是:未压缩文件中的微小本地更改可能导致压缩文件中的非常大的差异,即 rsync 将不得不传输更多数据 - 如果网络带宽是瓶颈,并且存在通常是许多文件中的微小差异,这可能会导致整体结果变慢。
  • @knorv:您实际上可能是对的。尽管 rsync 使用 delta-transfer 算法 (samba.anu.edu.au/ftp/rsync/rsync.html),但压缩往往会改变文件的整个结构,这使得 rsync 的 delta-transfer 算法有些无效 (zsync.moria.org.uk/paper200501/ch01s03.html)。如果确实选择在 rsync 之前解压缩文件,至少使用 -z 选项告诉 rsync 在传输之前压缩数据。
  • @Michael Borgwardt:我只是进一步研究了它,也得出了这个结论。请参阅我对@knorv 的评论。
  • +1 使用网络解决停机问题。是的,这意味着将新版本投入生产需要更长的时间,但如果最大限度地减少停机时间很重要,这是唯一真正可行的方法。您甚至可以在同一主机上的不同端口上将新版本作为单独的 tomcat 进程启动 - 然后将网络流量切换到该端口,并在旧版本连接消失后关闭旧版本。当然,如果进程崩溃或盒子死机,这对您没有帮助。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    已经注意到 rsync 在将更改推送到 WAR 文件时无法正常工作。原因是 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

    【讨论】:

    • 将库移动到共享目录的一个巨大问题是它们是否包含对网络应用程序中对象的引用。如果是这种情况,那么它们阻止 JVM 回收 Web 应用程序使用的空间,从而导致 permgen 耗尽。
    • 但是如果共享库没有包含对 webapp 对象的引用的静态变量,那么第二种方法是可以的,对吧?
    • 当然。但是你怎么知道?例如,JDK 的 Introspector 类缓存类定义,这意味着如果您从 Web 应用程序使用它,则必须在重新部署时显式刷新缓存。但是,如果您的共享编组库在后台使用 Introspector 怎么办?
    • “但你怎么知道?”。通过手动或自动检查代码。 (编写一个实用程序来检查 JAR 文件中的类是否存在潜在的麻烦静态是可行的。)
    【解决方案4】:

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

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

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

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

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

    我希望这会有所帮助。

    【讨论】:

      【解决方案5】:

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

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

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

      【讨论】:

      • 这应该可以避免我对 OP 实践的最大担忧,即非原子更新。
      • 是的,爆炸版本和热部署适合开发模式,但在生产中最好使用战争。
      【解决方案6】:

      【讨论】:

      • JavaRebel 现在更名为 JRebel
      • 使用 JRebel 技术进行生产级更新是一个名为 LiveRebel 的工具。
      【解决方案7】:

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

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

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

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

      【讨论】:

        【解决方案8】:

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

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

        • 一个带有 Apache 静态文件的大型 ZIP 文件
        • 为 servlet 引擎提供了一个较轻的 WAR。

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

        如果你成功地制作了一个轻量级的 WAR,我就不会费心 rsync 目录而不是存档。

        这种方法的优势:

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

        这种方法的缺点:

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

        【讨论】:

          【解决方案9】:

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

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

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

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

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

          【讨论】:

          【解决方案10】:

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

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

          【讨论】:

            【解决方案11】:

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

            【讨论】:

              【解决方案12】:

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

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

              【讨论】:

              • +1。这是我们使用的解决方案。通过一点点智能,您可以确保混合运行版本 N 和版本 N-1 的服务器集群将正常运行。然后只需将您的一台服务器脱机,升级它,然后将其重新联机。运行一段时间以确保没有问题,然后对其他一半的服务器执行相同的操作。像这样运行几天,这样你就有了一个退出位置,然后转换其余的位置。
              【解决方案13】:

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

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

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

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

              【讨论】:

                【解决方案14】:

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

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

                这样你就可以让 git 弄清楚哪些文件要传输到服务器。如果它被破坏,你也有一个很好的方法来退出它,只需进行还原!

                【讨论】:

                  【解决方案15】:

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

                  https://gist.github.com/3985742

                  【讨论】:

                    【解决方案16】:

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

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

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

                    【讨论】:

                      【解决方案17】:

                      只需使用 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

                      【讨论】:

                        【解决方案18】:

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

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

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

                        【讨论】:

                          猜你喜欢
                          • 2014-03-30
                          • 1970-01-01
                          • 1970-01-01
                          • 2010-11-25
                          • 2017-07-06
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多