使用 Java、Javalin 和 GraalVM 构建 22 MB 的微服务
Oracle的GraalVM允许提前(AOT)编译JVM应用程序。这意味着,编译器不会运行 JVM 进程来执行应用程序,而是构建本机二进制文件。它是如何工作的?在非常高的层次上,基本运行时(称为SubstrateVM)被编译到二进制文件以及实际应用程序中。听起来有点像Go,它还包括一个小的运行时,例如垃圾收集。
在本文中,我将展示如何使用 GraalVM 本机编译构建一个小示例 restful Web 服务。示例应用程序是用 Java 编写的。
为什么有人甚至会对JVM应用程序的本机编译感兴趣?遗憾的是,在E.ON的日常工作中,我仍然需要大量使用Java应用程序。我们的技术堆栈完全是云原生的 – 我们几乎在 Kubernetes 上运行所有内容。我们的应用程序是“典型的”12 因素应用程序。在这样的 dockerized 环境中,我认为原生编译会很有趣有三个主要原因。
应用程序启动时间
这可能不完全是Java的错。我们正在使用Spring Boot,启动速度非常慢。我通常必须调整我的 Kubernetes 就绪探测,使其在启动 Pod 后不要早于 20 秒进行检查。这适用于一个非常小的应用程序 – 500 行代码。
内存占用
根据我的经验,您不希望为Spring Boot应用程序提供少于512MB的内存。否则,可能需要几分钟才能启动。虽然Java,尤其是JVM开销是罪魁祸首,但这也是一个框架问题。众所周知,春天非常臃肿,并且使用了很多反射。
应用程序大小
Java 应用程序容器的大小是另一个问题。这很烦人,但并不重要。我能找到的最小的JRE是~65MB大(基于阿尔卑斯山的openJDK)。如果你使用 Spring Boot,你至少会有一个 ~40MB 的大胖罐子用于你的应用程序。如果你的应用更大,它显然会更多。每个容器至少为 100MB。请注意,Docker 映像的 JRE 层可能会被多个 Docker 映像重用,因此对于应用的每个映像,它并不是真正的 100MB+。虽然我认为在 2018 年拥有 100MB+ 大型 hello world 应用程序肯定是可以忍受的,但如果我可以拥有一个 6MB 的 Go 二进制文件,那就太弱了。
GraalVM AOT 编译可能会改善这种情况。我希望启动时间几乎是即时的,不需要JVM,并且应用程序大小会大大减小。GraalVM 有一些严重的局限性,因为 JVM 的几个功能不能很好地与静态编译配合使用。
完整列表可以在这里找到。文档在这里非常清楚:动态类加载是受支持,将来也不会受支持。相反,编译器会分析代码并将所有必需的类编译到二进制文件中。结合反思,这成为当前Java生态系统的噩梦。许多库和框架使用反射来动态实例化类。GraalVM 不能很好地处理这个问题,在许多情况下,必须提供额外的编译器配置。其中一个原因是,对 Class.forName() 的调用可能基于运行时信息。一个非常简单的例子:
if (someVariable) {
Class.forName("SomeClazz")
…
}
由于someVariable的值在编译时是未知的,编译器无法知道是否包含“SomeClazz”。更不用说它只是一个字符串,编译器必须在编译时在类路径上搜索这个类。如果编译器决定包含此类,它将这样做,并在找不到该类时抛出错误。那很好。但是,这只是尽力而为。不能保证在编译时包含所有必需的类 – 这意味着类可能会丢失,并且在实例化时会引发运行时错误。还有更多的限制,要完整参考,请转到GraalVM的文档。
作为概念证明,我正在寻找一个没有过度使用 反射的休息库。显然这不是弹簧靴——我选择了 javalin.io。它只是码头顶部的一个休息图书馆,仅此而已。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/292510.html