28

我们有一个运行在多台计算机上的 Java 服务器应用程序,所有计算机都连接到 Internet,其中一些位于防火墙后面。我们需要从一个中心站点远程更新 JAR 文件和启动脚本,而不会对应用程序本身造成明显的中断。

该过程必须是无人值守和万无一失的(即,我们不能因为不及时的互联网中断而破坏应用程序)。

过去,我们使用各种外部脚本和实用程序来处理类似的任务,但由于它们有自己的依赖关系,结果更难维护且可移植性较差。在制作新东西之前,我想从社区获得一些意见。

有没有人已经找到了一个好的解决方案?有什么想法或建议吗?

澄清一下:这个应用程序是一个服务器,但不适用于 web 应用程序(这里没有 webapp 容器或 WAR 文件)。它只是一个自主的 Java 程序。

4

9 回答 9

10

您没有指定服务器应用程序的类型 - 我将假设您没有运行 Web 应用程序(因为部署 WAR 已经完成了您所说的操作,并且您很少需要 Web 应用程序来执行拉取类型如果您谈论的是 Web 应用程序,则以下讨论仍然适用 - 您只需为 WAR 文件而不是单个文件实现更新检查和 ping-pong)。

您可能想看看 jnlp - WebStart 基于此(这是一种客户端应用程序部署技术),但我很确定它可以定制为执行服务器类型应用程序的更新。无论如何,jnlp 在提供描述符方面做得很好,可用于下载所需 JAR 的所需版本......

对此的一些一般想法(我们在同一个存储桶中有几个应用程序,并且正在考虑自动更新机制):

  1. 考虑在启动应用程序之前拥有一个能够读取 jnlp 文件并下载所需/更新的 jar 的 bootstrap.jar 文件。

  2. JAR 文件即使在应用程序运行时可以更新(至少在 Windows 上,这是最有可能锁定运行文件的操作系统)。如果您使用自定义类加载器,或者您有一堆可能随时加载或卸载的 JAR,您可能会遇到问题,但是如果您创建机制来防止这种情况,那么覆盖 JAR 然后重新启动应用程序应该是足以更新。

  3. 即使可以覆盖 JAR,您也可能需要考虑为您的 lib 路径使用 ping-pong 方法(如果您尚未将应用启动器配置为自动读取 lib 文件夹中的所有 jar 文件并将它们添加到类路径,那么这就是你真正想做的事情)。以下是乒乓球的工作原理:

应用程序启动并查看 lib-ping\version.properties 和 lib-pong\version.properties 并确定哪个更新。假设 lib-ping 有更高版本。启动器搜索 lib-ping*.jar 并在启动期间将这些文件添加到 CP。当您进行更新时,您将 jar 文件下载到 lib-pong 中(或者如果您想节省带宽并且 JAR 并没有实际更改,则从 lib-ping 复制 jar 文件 - 不过,这很少值得付出努力!)。将所有 JAR 复制到 lib-pong 后,您要做的最后一件事是创建 version.properties 文件(这样可以检测并清除导致部分 lib 文件夹的中断更新)。最后,您重新启动应用程序,引导程序会发现 lib-pong 是所需的类路径。

  1. 如上所述的 ping-pong 允许回滚。如果你设计得当,你可以拥有一个你测试的应用程序,然后永远不要更改该检查以查看它是否应该回滚给定版本。这样,如果您确实搞砸并部署了破坏应用程序的东西,您可以使版本无效。这部分应用程序只需从坏的 lib-* 文件夹中删除 version.properties 文件,然后重新启动。保持这部分污垢简单很重要,因为它是您的故障保险。

  2. 您可以拥有超过 2 个文件夹(例如,而不是 ping/pong,只拥有 lib-yyyymmdd 并清除除最新的 5 个以外的所有文件夹)。这允许对 JAR 进行更高级(但更复杂!)的回滚。

于 2008-09-24T05:18:29.150 回答
6

您绝对应该看看 OSGi,它是为这些情况(尤其是嵌入式产品)而创建的,并且被大量公司使用。您可以在应用程序运行时更新 jar“捆绑包”、添加和删除它们。我自己没有使用过,所以我不知道开源框架/服务器的质量,但这里有一堆有用的链接可以帮助你入门:

http://www.osgi.org/Main/HomePage
http://www.aqute.biz/Code/Bnd
http://blog.springsource.com/2008/02/18/creating-osgi-bundles/
http: //blog.springsource.com/
http://www.knopflerfish.org/
http://felix.apache.org/site/index.html

于 2008-09-24T06:27:49.293 回答
4

我推荐 Capistrano 进行多服务器部署。虽然它是为部署 Rails 应用程序而构建的,但我已经看到它成功地用于部署 Java 应用程序。

链接: Capistrano 2.0 不仅适用于 Rails

于 2008-09-24T04:29:13.823 回答
4

使更新原子化非常困难,特别是如果您有任何数据库更新要做。

但是,如果您不这样做,您可以做的是首先确保您的应用程序可以从相对路径运行。也就是说,您可以将您的应用程序放在某个目录中,并且所有重要文件都相对于该位置找到,因此您的实际安装位​​置并不重要。

接下来,复制您的安装。现在,您拥有“运行”版本和“新”版本。

使用您喜欢的任何技术(FTP、rsync、纸带、任何您喜欢的技术)更新“新”版本。

验证您的安装(校验和、快速单元测试,无论您需要什么——如果您愿意,甚至可以在测试端口上启动它)。

当您对新安装感到满意时,关闭原始运行实例,重命名原始目录(mv application application_old),重命名 NEW 目录(mv application_new application),然后重新启动它。

您的停机时间减少到服务器关闭和启动时间(因为重命名是“免费的”)。

如果您偶然发现一个严重错误,您的原始版本仍然存在。停止新服务器,将其重命名,重新启动旧服务器。非常快的回落。

另一件好事是您的服务基础设施是静态的(如您的 rc 脚本、cron 作业等),因为它们指向“应用程序”目录,并且它不会改变。

它也可以通过软链接而不是重命名目录来完成。无论哪种方式都很好。

但是该技术很简单,如果您的应用程序配合使用,则几乎是无懈可击的。

现在,如果您有数据库更改,那将是完全不同的讨厌问题。理想情况下,如果您可以使您的数据库更改“向后兼容”,那么希望旧的应用程序版本可以在新模式上运行,但这并不总是可能的。

于 2008-09-24T06:04:58.243 回答
3

当 JVM 在其上运行时,无法修改 Jars,这将导致错误。我已经尝试过类似的任务,我想出的最好的方法是复制更新的 Jar 并将启动脚本转换为查看该 Jar。一旦你有了更新的 Jar,启动它并等待旧 Jar 在发出信号后结束。不幸的是,这意味着在几秒钟内丢失 GUI 等,但是在 java 中序列化大多数结构很容易,并且当前的 GUI 可以在实际关闭之前转移到更新的应用程序(尽管有些东西可能无法序列化!)。

于 2008-09-24T04:30:04.397 回答
3

我会使用 ansible 将 jar 分发到多个服务器并运行更新脚本。

对于用户来说不明显的更新,应用程序必须停止旧实例并快速启动一个新实例,其中 Java 应用程序不是很好。

有很多方法可以在不停机的情况下进行更新,但它们都不是免费的。选择一个解决方案时,您应该考虑您可以接受的停机时间以及实现它的成本。

我看到两个选项:

  1. 滚动更新,这意味着每次更新时都会使用新版本启动一个实例,然后验证它是否正常工作,如果是,则终止旧版本。此解决方案将需要某种代理(例如 haproxy),它将流量引导到有效实例。
  2. 优化应用程序启动时间。

正如 Will Hartung 提到的,您还必须考虑外部依赖项,例如您可能还想更新的数据库模式。为了获得无缝更新,您可能还希望对数据库进行滚动更新。它要求您不要在应用程序的任何两个连续版本之间引入任何重大更改。例如。当您要删除列时,在第一个版本中,您删除应用程序中对该列的所有引用,在下一个版本中,您可以删除数据库中的列。

于 2020-07-21T18:04:42.083 回答
2

我相信如果您使用像SpringSource dm Server这样的基于 OSGi 的应用服务器,您可以热部署 JAR 文件。我自己从未使用过它,但知道 Spring 产品组合的一般质量,我相信它值得一看。

于 2008-09-24T05:25:55.247 回答
0

我们使用的是OSGi的更新系统Eclipse,体验非常好。

受到推崇的!

于 2008-09-24T07:36:44.747 回答
0

最新版本的 Java Web Start 允许在本地缓​​存中注入应用程序而无需实际调用程序,并且可以将其标记为“离线”。由于缓存是用来调用程序的,它只会在下次运行时更新。为此,您很可能需要名称中带有版本号的 jar(例如 our-library-2009-06-01.jar)。

于 2009-06-01T16:03:27.077 回答