我们如何远程更新Java应用程序?
我们有一个Java服务器应用程序,该应用程序可以在许多计算机上运行,所有计算机都连接到Internet,其中一些位于防火墙后面。我们需要从一个中央站点远程更新JAR文件和启动脚本,而不会明显中断应用程序本身。
该过程必须无人值守且万无一失(即由于互联网中断造成的时间我们无法中断应用程序)。
过去,我们使用了各种外部脚本和实用程序来处理类似的任务,但是由于它们具有自己的依赖性,因此结果难以维护且移植性较差。在制作新内容之前,我希望获得社区的一些意见。
有没有人为此找到好的解决方案?有任何想法或者建议吗?
需要说明的是:此应用是服务器,但不适用于Web应用(此处没有webapp容器或者WAR文件)。它只是一个自治的Java程序。
解决方案
我建议Capistrano用于多服务器部署。虽然它是为部署Rails应用程序而构建的,但我已经看到它已成功用于部署Java应用程序。
关联:
Capistrano 2.0不仅适用于Rails
在JVM之上运行Jar时,无法对其进行修改,这将导致错误。我尝试了类似的任务,而我想出的最好的办法是制作更新的Jar的副本,并转换启动脚本以查看该Jar。有了更新的Jar之后,请启动它,并在发出提示后等待旧的Jar结束。不幸的是,这意味着一秒钟会丢失GUI等,但是序列化Java中的大多数结构很容易,并且当前的GUI可以在实际关闭之前转移到更新的应用程序中(尽管有些东西可能无法序列化!)。
我们没有指定服务器应用程序的类型,我假设我们没有在运行Web应用程序(因为部署WAR已经完成了我们所谈论的内容,并且我们几乎不需要Web应用程序来进行拉类型更新如果我们正在谈论一个Web应用程序,那么下面的讨论仍然可以应用,我们只需为WAR文件而不是单个文件实施更新检查和乒乓即可。
我们可能想看看jnlp WebStart是基于此的(这是一种客户端应用程序部署技术),但是我很确定它可以针对服务器类型应用程序执行更新而定制。无论如何,jnlp在提供可用于下载所需JAR的所需版本的描述符方面做得非常好。
对此有一些一般性的想法(我们在同一个存储桶中有多个应用程序,并且正在考虑自动更新机制):
- 在启动应用程序之前,请考虑具有bootstrap.jar文件,该文件能够读取jnlp文件并下载所需的/更新的jar。
- 即使在应用程序运行时(至少在Windows上,并且OS最有可能在运行的文件上保持锁定),都可以更新JAR文件。如果我们使用的是自定义类加载器,或者随时都有可能加载或者卸载一堆JAR,则可能会遇到问题,但是如果我们要创建防止这种情况的机制,则应覆盖JAR,然后重新启动应用足以进行更新。
- 即使有可能覆盖JAR,我们也可能需要考虑使用lib路径的乒乓方法(如果尚未将应用启动器配置为自动读取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是所需的类路径。
- 如上所述的乒乓允许回滚。如果设计正确,则可以让应用程序中的一部分经过测试,然后再进行更改,以检查它是否应回滚给定版本。这样,如果我们搞砸了并部署了破坏应用程序的内容,则可以使版本无效。应用程序的这一部分只需要从错误的lib- *文件夹中删除version.properties文件,然后重新启动即可。重要的是要使这部分污物简单,因为这是故障保护。
- 我们可以拥有2个以上的文件夹(例如,代替ping / pong,仅拥有lib-yyyymmdd并清除除最新的5个文件夹之外的所有文件夹)。这允许对JAR进行更高级(但更复杂!)的回滚。
我相信,如果我们使用像SpringSource dm Server这样基于OSGi的应用服务器,则可以热部署JAR文件。我从未亲自使用过它,但是了解Spring产品组合的一般质量,我相信它值得一看。
使更新原子化是非常困难的,特别是如果我们要进行任何数据库更新时。
但是,如果不这样做,我们首先需要确保应用程序可以从相对路径运行。也就是说,我们可以将应用程序放置在某个目录中,并且所有重要文件都是相对于该位置找到的,因此实际安装位置并不是很重要。
接下来,复制安装。现在,我们具有"运行"版本,而我们具有"新"版本。
使用任何我们喜欢的技术(FTP,rsync,纸带,任何浮标)更新"新"版本。
验证安装(校验和,快速的单元测试,无论我们需要什么,甚至可以根据需要在测试端口上启动它)。
当我们对新安装感到满意时,请在原始运行实例上重新命名原始目录(MV应用程序application_old),重命名NEW目录(MV Application_new应用程序),然后将其备份。
停机时间减少到服务器关闭和启动时间(因为重命名为"免费")。
如果偶然发现严重错误,则原始版本仍然存在。停止新服务器,将其重命名,然后重新启动旧服务器。非常快地回退。
另一个好处是,服务基础架构是静态的(例如rc脚本,cron作业等),因为它们指向" application"目录,并且不会更改。
也可以使用软链接来完成,而不是重命名目录。两种方法都可以。
但是该技术很简单,并且如果应用程序可以配合的话,也几乎可以证明。
现在,如果我们有数据库更改,那将是完全不同的令人讨厌的问题。理想情况下,如果我们可以使数据库更改"向后兼容",那么希望旧的应用程序版本可以在新的架构上运行,但这并不总是可能的。
我们绝对应该看一下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
我们使用Eclipse作为OSGi的更新系统,我们的经验非常好。
推荐的!
最新版本的Java Web Start允许在不实际调用程序的情况下将应用程序注入本地缓存中,并且可以将其标记为"离线"。由于缓存是用于调用程序的缓存,因此将仅在下一次运行时对其进行更新。为此,我们很可能需要名称带有版本号的jar(例如our-library-2009-06-01.jar)。