作者 | Jason Greene, John Clingan, Eric Deandrea
译者 | 平川
策划 | 丁晓昀
Java 在企业应用中占据主导地位。但在云上,Java 比一些竞争对手的使用成本更高。原生编译降低了在云中使用 Java 的成本:它创建的应用程序启动速度更快,占用的内存更少。
原生编译给 Java 用户带来了许多问题:原生 Java 对开发有什么影响?我们什么时候应该改用原生 Java?什么时候不应该?我们应该使用什么框架来开发原生 Java?这个系列将回答这些问题。
1 Kubernetes Native Java 是什么?
“[它们] 可以运行 3 倍密度的部署,而又不影响服务的可用性和响应时间。”—— Lufthansa Technik
采用 Kubernetes,组织可以更快、更经济有效地交付业务价值。为什么?因为 Kubernetes 简化了一些原本困难的任务:它会在负载增加时自动实现应用程序的水平扩展,并能执行滚动升级。
但是,这种方法与 Java 传统的三层架构模型(大堆、垂直扩展、长时间运行的进程)相冲突。
历史上,Java 通过分配和最大化持有的系统资源,来适应随时间变化的负载,实现卓越的性能。
开发人员非常喜欢长时间运行的、重量级的、JVM 高度动态的 Java 模型,以便在运行多个不同的应用程序时可以支持丰富的声明式编程模型。延迟扫描和运行时类路径分析给我们带来了控制反转,减少了样板代码——以启动时间和内存占用为代价。这些缺点在堆很大的大型单体应用程序中并没有太大影响,因为启动代价只需要支付一次。
但是,在 Kubernetes 中运行这些传统的 Java 应用程序会导致许多胖 JVM,它们承载着需要大量资源的动态运行时,并且会经常重启。重启要一次次分析同样的不可变镜像,每次都会浪费掉相同的 CPU 周期。这个分析还会使用大量的内存,而且在 JVM 生命周期中一直占用。但是在 Kubernetes 中,部署密度很重要:可以运行的应用程序实例越多,Kubernetes 的投资回报率就越高。
Kubernetes Native Java 意味着 Java 应用程序可以利用而不是抗拒 Kubernetes 的能力——启动速度很快的小进程,而不是启动时间很长、内存占用很高的 JVM。
2 Quarkus 背后的动机
“在我们推出 Quarkus 之前,许多客户已经开始关注 Go 和 Node.js 等技术栈,希望可以借此提高性能和效率。对于选择一种新的语言以及雇用新的开发人员,或者重新培训现有的 Java 开发人员,这些客户都倍感疲倦。” —— Arijit Mazumdar,WiPro
组织以及开发人员都在 Java 上有很大的投资。它是一种令人难以置信的高产语言,有许多开发人员熟悉并喜爱的神奇工具和框架。然而,Kubernetes 需要一个新的方向。虽然放弃 Java 的许多好处令人遗憾,但能更有效利用内存、缩短启动时间的运行时,如 Node.js 和 Golang,对 Java 投资发起了挑战。
Quarkus 团队则希望两者兼得:将功能丰富、成熟的 Java 生态系统的优势与 Kubernetes 的操作优势相结合。要实现这一点,Java 应用程序运行时必须更多地采用静态定义,但仍然包含让开发人员可以更快迭代的 Java 动态特性。
组织使用云增加了应用程序部署的速度。同样,现如今的业务需要缩短开发周期,加速业务价值交付。Quarkus 所采用的方法特别注重“开发乐趣(Developer Joy)”,可以支持迭代和部署速度的提升,并且已经证明 Java 可以比脚本语言更具生产力。
当然,生产力并不仅仅与编码速度有关。构建现代云应用程序需要与其他服务交互,而使用复杂 YAML 的配套技术会降低你的速度。我们认为,关乎“开发乐趣”的特性,比如开发服务和零配置开发(稍后会详细介绍),对于 Kubernetes Native Java 来说必不可少。例如,你编写访问数据库的代码,Quarkus 会在你输入的时候神奇地启动并连接后台的所有一切。
同样,向 Kubernetes 部署时也不需要阅读 Kubernetes 管理方面的书籍以及编写数百行的 YAML 文件。我们认为,对于 Quarkus,很重要的一点是你只需要了解 Java。在构建时添加“-Dquarkus.kubernetes.deploy”标志,Quarkus 就会将应用程序部署到 Kubernetes 上(不要忘记登录!)如果你愿意,可以使用集成的 Quarkus Dev UI,直接从浏览器触发部署。
简而言之,Quarkus 是从头开始构建的,它将 Java 变成了构建本地二进制文件和 Kubernetes 应用程序的理想语言。Quarkus 是 Kubernetes Native Java!
3 “构建时”促进原生编译
“Quarkus 引入了一种新的范式,它颠覆了底层的运行方式——支持原生模式,所有反射都是在编译时完成,而不是运行时完成,这太神奇了!” —— Roberto Cortez,Talkdesk(目前受雇于红帽公司)
按照 Kubernetes 原生架构的要求,将应用程序的生命周期划分为两个不同的阶段,是实现可瞬间启动的小型轻量级进程的关键。传统上,Java 应用程序运行时启动要执行一系列复杂的、长时间运行的、动态的自省步骤,以满足动态部署环境的要求。这些步骤在应用程序每次启动时都要重复进行。
由于容器镜像不会变化,所以不需要在运行时再执行这些步骤。大多数动态启动步骤都可以移到构建时。在实际开始时执行的工作将少很多,速度会明显加快。此外,还可以丢掉应用程序不需要的代码。通过对输出进行裁剪可以得到只包含必须代码的更精简的可执行文件。
当企业在评估和准备 Java 原生可执行程序时,Quarkus 直接带来了 JVM 效率方面的优势,因为构建时优势普遍适用:从这种方法中获益的不仅仅是原生编译,还有传统的 JVM/Hotspot。当代码较少、效率较高时,无论其形式如何,输出都会更精简、更快速。也就是说,本地提前编译将这种优势提升到了一个新的水平:在一个封闭的世界里,所有东西都是提前知道的,编译器可以进行非常精细的优化,一直到字段、变量和指令集级。
不过,有一个问题。为了使预(AOT)编译器有效地完成工作,它必须能够理解 Java 代码最终会做什么。Java 的动态性使其具有了很高的生产力,同时也使编译器无法掌握应用程序的行为,并限制了优化程度。例如,注入点使代码变得简单,易于演进。但对 AOT 编译器来说,它是一个未定义的、待明确的东西。
例如,你可以通过反射将每个类标记为可用,从而轻松得到一个原生可执行文件。但结果可能会让人失望:内存使用量和启动时间只比 JVM 上好一点点。换句话说,你需要一个全面的构建时模型来释放原生编译的所有优势。就像 Quarkus 所做的那样!
现在,Quarkus 在构建时解析了每个依赖关系,并生成了一个完整而封闭的应用程序。前面提到的注入示例的问题,从 AOT 编译器的角度来说,已经完全解决了——它现在知道要使用什么类,其余的代码可以去掉了。
总之,将 GraalVM 的优化能力与 Quarkus 的构建时能力相结合,可以获得最小的内存占用、最短的启动时间。限制这种开销对于释放 Kubernetes Native Java 的全部优势至关重要。
4 一流的本地编译
“现在,我们是在容器上以 JVM 模式使用 Quarkus,但将来,我们已经计划在 Kubernetes 和无服务器环境中以原生模式使用 Quarkus。在这些环境中,原生模式将是理想的选择。” —— Edouard Lamotte,Sedona
由于 Quarkus 及其扩展(Quarkus 优化过的库)包含了构建时优化,所以你可以将任何应用程序构建为一个内存占用较小的本地可执行文件。Quarkus 负责所有的工作!其他解决方案通常需要更多的开发人员参与:也许你可以编译成本地程序,但前提是你的应用程序使用了明确支持的 API 子集,或者你用了 GraalVM 提示(hints)注解代码。即使你遵循 GraalVM 的规则,也经常需要保证一个单独的 JSON 配置文件得到及时更新。此外,Quarkus 使开发者能够轻松编写针对本地可执行文件的测试,确保你构建的东西(本地可执行文件)按预期运行。
我们认为,平台对原生编译的广泛支持对于有效使用该技术至关重要。出于这个原因,我们不遗余力地简化原生编译的采用。此外,我们确保 Quarkus 中的每个扩展都支持它。我们强烈建议所有“Quarkiverse”扩展(社区贡献的扩展库)也支持原生编译。并非所有的 Java 库都与 GraalVM 的原生编译兼容。然而,由于 Quarkus 有大量的扩展(将近 500 个,而且还在不断增加!),所以很可能你能想到的所有用例都已经覆盖到了。如果没有,为你的用例编写一个 Quarkus 扩展也并不困难。
5 开发乐趣
“在提升开发体验方面,Quarkus 一直有一些相当惊人的创新。他们的开发模式控制台和 Testcontainer 支持将其提升到了下一个等级。” —— James Ward,Java Champion
如前所述,Kubernetes Native Java 并不仅仅是为了精简可运行文件。它对于提高 Java 的生产力,扩大其相对于其他原生编译语言(如 Golang)的生产力优势,同样至关重要。每一种工具、框架和运行时都声称它能提高开发者的生产力。但 Quarkus 通过提供“开发乐趣”将其提升到了一个新的水平。这是什么意思呢?
Quarkus 致力于提供一种有趣的、令人愉快的开发体验,保证 Java 开发者持续参与。Quarkus 的目标是让每个开发者都专注于活跃的编码,因为我们知道,停下来,运行一堆工具,等事情完成,会产生很大的反作用。Quarkus 让开发者非常高效,以至于他们会愿意去做各种尝试——以前,由于截止日期的限制,这可能是不被允许的。以下是几个可以带来“开发乐趣”的特性:
当这些功能结合在一起时,就可以加快开发团队的速度,使他们能够专注于他们最擅长的工作。这就加速了项目的交付,更快地满足客户需求,最终通过把更多的时间花在业务逻辑上而不是管道上来提高应用程序的质量。
6 命令式 vs. 反应式
“[Quarkus] 天然可以处理反应式或命令式编程。” —— LogicDrop
运行时性能很重要。由于整体资源使用率较低,所以反应式编程模型的扩展效率更高,响应性也更强。但是,实现运行时性能最大化不应该要求重写,也不应该以损失易用性和开发灵活性为代价。因此,Quarkus 建立在以 Eclipse Vert.x 工具包为基础的统一的阻塞 / 反应式 I/O 栈上。
Quarkus 实现了智能路由,使命令式和反应式 API 的结合成为一种常规的开发体验。
当一个应用程序使用 Quarkus 的反应式 API 时,代码在 I/O 线程上运行。这就减少了线程上下文切换,并在最大程度上提高了吞吐量,同时最大程度地减少了资源消耗。
当使用命令式 API 开发时,Quarkus 将工作分派给工作线程,完成后再将工作切换回 I/O 线程。
即使在同一个应用程序(或同一个类)的开发中,也可以使用阻塞的命令式 API 或异步的反应式 API。借助 Quarkus 智能路由,Quarkus 天生就为这两种方法并存提供了完美的支持。开发人员不需要在编写代码之前就做出选择。
7 再见了,样本代码!
“[我看到] 开发生产力提升了 30% 到 40%” —— Christos Sotiriou,Vodafone Greece
Quarkus 社区坚信,你在 Quarkus 项目中输入的所有内容都应该是超级简洁且容易理解的,并且不需要过多的使用光标键!Quarkus 包括了许多流行的 API,并对它们做了增强,以便它们可以利用 Quarkus 的构建时信息。
例如,Quarkus 用“Panache”增强了 Hibernate,这是一组 API 扩展,使数据访问像现代 Java 一样自然。这段代码片段就是表示 JPA 实体所需的全部内容。没有 getter,没有 setter,没有样板代码!
@Entity
public class Person extends PanacheEntity {
public String name;
public LocalDate birth;
public Status status;
}
现在,查询数据库也很简单:
List<Person> allPersons = Person.listAll();
Quarkus 扩展可以检测到其他扩展的存在并与之集成,减少需要编写的代码量,趋向于我们认为的最佳模式和实践。
例如,如果“Panache”代码片段属于一个包含数据源和健康检查扩展的应用程序,那么数据源扩展将自动为应用程序定义数据库健康检查和 Kubernetes 就绪探针。因此,当数据库不可用时,Kubernetes 将不会向应用程序容器发送流量。
甚至更进一步,如果同一个应用程序包含一个指标扩展,数据源扩展将自动把指标暴露给 Prometheus 这样的监测工具。
8 将 Kubernetes 作为一等支持平台
“这是否意味着我可以回去写代码了?我不用再处理 [Kubernetes] 服务配置,因为 Quarkus 帮我做了。” —— DevOps 工程师,LogicDrop
Quarkus 天生就是 Kubernetes Native 的,其运行的高效率来自于 Java 字节码和本地可执行文件。此外,Quarkus 将 Kubernetes 作为一等支持平台,并提供了以下扩展和能力:
9 Quarkus 入门
“我能够非常迅速地准备好 Quarkus 并运行。Quarkus 指南提供了很大的帮助,因为它们专注于一个主题,而且直接了然。” —— Victor Gallet,高级开发工程师和 Kafka 全能专家
要想了解更多的信息,最好的方法是创建你的第一个 Quarkus 应用程序。只需要几分钟就可以,只有四个步骤!请查阅入门指南,它将引导你完成每个步骤。Spring 开发者可以通过阅读免费电子书 Quarkus for Spring Developers 快速掌握 Quarkus。
之后,你可以使用我们的网站 code.quarkus.io:只要选择你想要的扩展,并生成项目就可以了。
10 小结
Kubernetes Native Java 关乎重新定义使用 Java 包含 Kubernetes 模型的方法,在共享环境中,通过减少启动时间和内存使用率、提高资源效率来降低成本。Quarkus 通过将大部分的启动处理转移到构建时来实现这一目标。其结果是一个高效的运行时,通过资源效率显著提升的应用程序大幅降低云计算成本。此外,企业可以提供直接影响业务的新功能,而又不会超出他们目前的云计算预算。
为了最大限度地发挥原生编译的优势,Quarkus 实现了与 GraalVM 的紧密结合。应用程序经裁剪后,可以充分利用提前编译带来的优化。Quarkus 使用 GraalVM 的扩展模型增强了这一过程,并避免了额外的配置。
Quarkus 提供的“开发乐趣”使开发人员通过实时编码、开发服务和持续测试等功能持续参与解决业务问题。使用 Quarkus 进行开发的效率非常高,Java 开发者可以尝试多种解决问题的方法,并选择对业务最有利的一种。
Quarkus 还将 Kubernetes 视为一等支持平台。它提供了一系列 API 框架,使应用程序能够充分利用 Kubernetes 提供的东西。这实现了 Kubernetes 投资的最大化,并通过平台整合缩短了上市时间。
开始吧!
作者简介:
Jason Greene 是红帽公司的杰出工程师兼应用运行时首席架构师。他是 Quarkus 的联合创始人和共同负责人。该项目旨在革新云端微服务和无服务器应用程序 Java 开发。在此之前,他曾领导 WildFly(业界领先的开源 EE 应用服务器,正式名称为 JBoss AS)项目长达近十年。在红帽任职期间,他曾参与许多中间件领域的工作,包括服务器运行时、集群、Web 服务、AOP 和安全。他是开源的坚定支持者,在职业生涯中为许多项目做过贡献。他也是 JCP 和 Eclipse Jakarta 的成员,他曾代表红帽公司参加 EE 专家组及相关标准的制定。他的兴趣包括并发性、分布式计算、硬件和编程语言设计。
John Clingan 是红帽公司的高级首席产品经理,专注于标准和下一代平台,如 Quarkus。他也是 MicroProfile 的共同创始人和指导委员会目前的负责人。John 有 30 年的行业经验(他年纪有点大了),包括软件开发、系统管理、技术销售和咨询,以及产品管理。他还有 10 多年的授课经验,为加州州立大学和加州大学继续教育学院教授 Java 课程。最近,他还为 O'Reilly 开展了 MicroProfile 和 Quarkus 的现场培训。John 是“Kubernetes Native Microservices with Quarkus and MicroProfile ”一书的作者之一。
Eric Deandrea 是红帽公司的高级首席开发大使,专注于应用开发技术。Eric 有超过 22 年的经验,为金融服务和保险行业设计和制定基于 Java 的解决方案和开发培训计划。他也是各种开源项目的贡献者,包括 Quarkus 和 Spring。最近,Eric 将他的 Quarkus 和 Spring 知识付诸实践,出版了他的第一本书 “Quarkus for Spring Developers”。他喜欢用以 DevOps 为中心的自动化技术来简化相关工作。工作之余,Eric 喜欢在新罕布什尔州的湖上划船。他还喜欢冰上曲棍球和武术,并有空手道黑带。
原文链接:
https://www.infoq.com/articles/native-java-quarkus/