使用Quarkus Native将Vaadin应用程序作为本地可执行文件的方法

101 阅读7分钟

使用Quarkus Native将Vaadin应用作为本地可执行文件

本地编译是JVM生态系统中的一个热门话题。虽然它为你想动态扩展的小型服务的部署提供了很好的优化,但它是否适合更大、更复杂的Java应用呢?

JVM是一个怪物--从好的方面来说。它的架构,尤其是超级优化的及时编译器(JIT)帮助Java字节码应用程序以一种让初创公司长大后重写整个软件的性能运行。但这一惊人的工程并不是没有代价的。众所周知,即使是小型的JVM应用程序也会占用大量的内存,而且非小型JVM应用程序的启动时间是--嗯--标志性的。

GraalVM是Oracle实验室推出的一个新的JDK发行版。它使用的代码库与常规的OpenJDK部分相同,你很可能每天都在使用它的一个变体,但它侧重于性能和多语言支持。在JVM性能方面,GraalVM用一个定制的版本取代了HotSpotJava虚拟机中的JIT编译器,特别是在企业版中,预计会有明显的性能提升(根据他们自己的基准,34%),包括一些内存节省。

与性能相关的更激进的功能是完全不使用JVM的运行能力。GraalVM包含工具(一个 "超前编译器"),将Java应用程序编译成本地可执行文件。虽然GraalVM中的JVM改进带来了明显的性能提升,而且JVM在服务器进程中仍然胜过本地程序,但本地编译在Java应用程序过去很糟糕的领域是一个游戏规则的改变。编译成本地代码的Java应用程序的启动/执行时间与原始的C应用程序相当,而且一个琐碎的应用程序所保留的内存只是JVM启动时所需的一小部分。

我最近花了一些时间为几个Vaadin例子设置了本地编译。这当然是可能的,但对于大多数用例来说,可能不值得这样做。让我们来讨论一下将你的Vaadin应用程序编译为本地镜像需要什么,它对性能特性意味着什么,以及你是否应该/何时考虑。

如何编译为本地程序

在创建本地镜像时,GraalVM将你的字节码、JDK和自定义虚拟机,全部编译成你的操作系统和处理器可以接受的本地二进制文件。听起来很简单,直到你听到限制:Java的动态功能并不是开箱即用的。本地编译需要一些提示,例如,在处理反射使用和从classpath访问资源时。因此,对于大多数Java应用程序来说,本地编译并不只是增加一个新的构建步骤。

由于我们使用的许多Java库都依赖于反射的使用,并且由于本地编译的整体复杂性,我建议从一个从头开始为本地编译而构建的堆栈开始。我为这个练习选择了Quarkus,我们最近为它增加了一个官方集成库。Quarkus有很好的说明,可以让你开始使用本地编译,大多数 "Quarkus扩展 "都包含了对GraalVM关于反射使用等的提示。

Vaadin扩展是其中之一,它还没有这些提示,所以当你第一次尝试它时,你可以期待在你的构建中出现一堆异常。为了克服Vaadin UI造成的编译问题,我遵循这些伟大的指示。要使你的非琐碎的应用程序编译为本地程序,唯一合理的方法是在运行该应用程序时使用一个特殊的JVM代理。这样,你就可以自动收集所需的提示,例如反射的使用。

对于某些部分,我改变了我的依赖性,以获得开箱即用的工具。我从一个嵌入式的H2数据库转移到一个独立的数据库和一个兼容的JDBC驱动。另外,我的演示数据生成在幕后使用了Node.js调用,所以让一个SQL脚本将测试数据填充到数据库中要容易得多。

除了DB支持的例子之外,我还做了一个Quarkus启动项目的版本,可以编译成本地的。它是相当琐碎的,但它也许可以帮助你让你自己的应用程序进行编译。

Quarkus Native附带了一堆用于创建本地图像的助手。在启动例子中,我手动将JVM代理生成的提示移到了一个Quarkus专用的Java类中,该类为工具链提供了同样的提示,而且是以位数的形式。在一个理想的世界里,Vaadin扩展会向核心库部分提供这些提示。如果你对这个功能感兴趣,请去给我生成的这个增强问题竖起大拇指。

本地编译的优点和缺点

Quarkus应用程序在普通的JVM中启动起来也很迅速。不过,一旦你把它编译成本地的,你肯定会看到它的不同。在我的例子中,即使使用Hibernate,启动时间也只有几分之一秒。

原生编译在某些方面肯定是有区别的,但它对Vaadin应用程序有意义吗?在我的测试中,我收集了以下利弊清单。

优点

  • 为进程保留的内存较少
  • 更小的人工制品
  • 启动速度更快
  • 没有 "预热期",JIT编译器在新进程的性能尚未达到最佳时需要预热。

缺点

  • 与预热的JVM相比,性能更差,在没有GraalVM的 "企业版 "的情况下,仍能轻松击败本地编译。
  • 构建时间要长得多;好的方面是,前端的webpack步骤让人感觉很轻松:-)
  • 更加复杂和脆弱的构建;你当然不能指望你的应用程序在你的Java IDE中清除了最后的编译问题后还能正常工作。
  • 与JVM工具(调试、JVM代理、配置)不兼容
  • 工具不再独立于平台;"一次编写,到处运行 "的承诺在某种程度上被打破。

虽然我对原生编译的Java应用的能力印象深刻,并能看到Java生态系统扩展到以前被排除在外的领域的巨大可能性,但我仍然认为JVM是大多数Vaadin应用的发展方向。对Vaadin应用程序来说,最大的好处可能是节省内存。但是,由于Vaadin的部署无论如何都喜欢内存,即使是使用原生的,我认为为你的服务器购买更多的内存比摆弄原生编译要便宜得多。这种权衡基本上和你当初选择Vaadin的原因是一样的:开发人员的生产力高于托管费用

不过,在某些情况下,内存的节省可能会变得很重要--比如你有很多(比如几十个或几百个)不同的Vaadin应用在运行(每个应用只有几个活跃用户)。例如,这可能发生在一个SaaS服务中,你选择为每一个客户单独部署。

不要错过 "另一个GraalVM "的可能性!

虽然我目前对建议我们的客户使用本地编译犹豫不决,但我想强调GraalVM的一件事,许多Java开发者都错过了。GraalVM !=本地二进制编译。GraalVM是JDK多种现代化努力的一个总括项目。

对大多数Java开发者来说,最相关的部分可能是用Java编写的新的即时编译器(JIT)。使用该编译器时,你将使用与普通JDK几乎相同的JVM,但你将在几乎所有方面获得更好的性能,尤其是企业版。但是对于开发过程和惯例来说,除了JDK的名字,其实没有什么变化。GraalVM项目支持Java 17,而且你可以购买这方面的支持,所以对于那些托管CPU密集型Java项目的人来说,它应该是一个可以考虑的安全选择。