Blog chevron_right Java

改进基于 Java 的微服务的三种方法

Is Java Right for the Challenges of Microservices?

Azul 的全部专注点在于开发 JVM。Azul Platform Prime 非常适合为基于 Java 的微服务部署提供更好的性能并降低成本

 在本文中,您将了解:

  • 您可以为基于微服务的应用程序实现更快的 Java 预热 
  • 更先进的 JIT 编译器可以进一步缩短预热时间 
  • 优秀的垃圾收集器可以消除那些对用户体验产生负面影响的暂停 

Azul Platform Prime 基于开源 OpenJDK 源代码,它是 Java SE 标准的参考实现。为了提供比传统 JVM 更优的性能,Azul 用替代实现替换了部分核心 JVM。具体来说,这包括内存管理系统和部分即时 (JIT) 编译系统。Platform Prime 通过三种方式提升基于 Java 的微服务性能:

  1. 使用 ReadyNow 加快预热
  2. 使用云原生编译器缩短预热时间
  3. 使用 C4 垃圾收集器减少暂停

更快的 JIT 编译可提升性能 

Java 源代码被编译为字节码。字节码是 Java 虚拟机 (JVM) 的指令。当 JVM 启动应用程序时,它必须解释每个字节码,将其转换为必要的本机指令和操作系统调用。这种解释会产生开销,导致代码的运行速度比静态编译的代码慢得多。 

为了消除这个问题,JVM 在解释字节码时对代码进行分析,记录调用方法的次数。JVM 使用这些分析数据来识别代码中的热点,然后可以使用即时 (JIT) 编译器将其编译为本机指令。随着时间的推移,应用程序执行的大部分代码都会被编译,但到达这一点所需的时间称为应用程序的预热时间。

关于 JIT 编译器
传统的 JVM 采用两个 JIT 编译器,称为 C1 和 C2(有时称为客户端和服务器)。每个 JIT 都有其独特的特性,决定了应用程序的性能。

  • C1 编译字节码速度很快,但代码优化程度较低。这非常适合需要快速预热的短期应用程序。C2 需要更长的编译时间,但会对其生成的代码进行更多优化。这更适合运行时间较长(通常是服务器)的应用程序,因为预热阶段可能需要几分钟,但这并非问题。

从 JDK 7 开始,两种 JIT 都可以通过分层编译用于同一个应用程序。初始阶段使用 C1;随着应用程序运行时间的延长,将使用 C2 来提高性能。 

Azul Platform Prime 使用名为 Falcon 的全新改进版本取代了 C2 JIT [图 1]。Falcon JIT 编译器基于开源 LLVM 编译器项目,该项目得到了包括 Intel、NVidia、Apple 和 Sony 在内的众多公司和个人的支持。

图 1:得益于 Falcon JIT 编译器和其他功能,Azul Platform Prime 21 在启动阶段的速度超过了 OpenJDK。来源:高性能 Java 平台基准测试研究

1.使用 ReadyNow 缩短预热时间 

由于每次应用程序启动时,JVM 都不知道之前执行过程中可能发生的情况,因此应用程序预热的问题更加严重。JVM 必须经历相同的性能分析阶段,执行相同的分析并编译相同的代码段。对于需要频繁重启的应用程序来说,这是有问题的。 

解决这个问题的一个显而易见的方法是,在应用程序达到稳定状态且所有必要的代码都已编译完成后,创建已编译代码的快照。应用程序重新启动时,可以重新加载此快照,应用程序可以继续运行,就像它从未停止过一样(从编译的角度来看)。不幸的是,事情并没有这么简单(尽管听起来很简单,但实现实际上很复杂)。JVM 的定义限制了启动时可能发生的事情,尤其是在类加载和初始化方面。其他一些问题使得这种方法不切实际。

ReadyNow 是 Azul Platform Prime 的一项功能,可从一开始就加速生产环境中的应用程序预热。此时,应用程序的 JIT 状态配置文件会被记录下来。此配置文件记录了当前已加载的类、已初始化的类、指令分析数据(例如 JVM 用于决定编译哪些代码段的数据)以及推测优化失败的详细信息。此外,还会保存已编译代码的副本。 

当应用程序重新启动时,可以使用此配置文件让 JVM 和 JIT 执行其通常在应用程序预热阶段执行的所有工作;在这种情况下,这些工作可以在应用程序开始执行 main () 方法中的代码之前完成。这几乎消除了预热阶段,因此应用程序在启动时就可以几乎全速运行 [图 1]。

1:使用 ReadyNow 缩短 Java 预热时间Now

2.使用云原生编译器进一步缩短预热时间  

JIT 编译提供了现代企业应用程序所需的性能,通常优于本机编译的代码,但性能是有代价的。JIT 编译工作负载必须与应用程序共享资源,以便它可以执行事务,这会增加预热时间并降低预热期间的应用程序性能。 

如果我们在云端运行,凭借其弹性资源和基于效用的定价模型,为什么不将 JIT 编译器的工作转移到其他地方? 

Azul 通过云原生编译器实现了这一点。 

云原生编译器创建了一个集中式的 JIT 即服务 [图 2]。当 JVM 需要编译某个方法时,它会将详细信息发送给云原生编译器,而不是在本地执行。这样可以在预热期间实现更大的吞吐量。云原生编译器还可以是一个日志运行进程,用于存储方法编译的结果。当请求同一方法时,启动多个微服务实例,而不是再次编译,可以立即返回存储的代码,从而进一步缩短预热时间。

图 2:云原生编译器将编译卸载到云端

3.使用 C4 垃圾收集器减少暂停

当执行 Java 应用程序时,JVM 自动处理内存管理。JVM 在实例化新对象时会在堆上分配空间,并在应用程序不再引用该对象时回收该空间。这就是垃圾收集 (GC) 的过程。GC 还会管理堆,通过定期压缩堆、移动对象以确保实时数据连续并消除碎片,从而优化新对象的可用空间。与 C 和 C++ 等依赖程序员显式释放应用程序已用空间的语言相比,GC 具有显著的优势。这种显式释放可能是许多应用程序错误的根源,尤其是当程序员尝试释放无效或不正确的地址时,会导致内存泄漏或应用程序突然终止。 

然而,在传统的 JVM 中,GC 也需要暂停应用程序线程,以避免在压缩期间移动对象时损坏数据。这种影响是双重的:首先,它会在 GC 执行工作时给应用程序带来暂停。这些暂停的时长可能从几毫秒到几小时不等,并且时长与分配给堆的内存量成正比,而不是与堆中的数据量成正比。

Azul Platform Prime 使用了不同的 GC 算法,
即连续并发压缩收集器 (C4)。与其他商用 GC 算法不同,
应用程序线程可以在 GC 执行其工作时继续运行
(因此名称中含有并发一词)。

其次,这些暂停发生的时间无法准确预测。这会给应用程序带来不确定性行为。在微服务的环境中,这可能导致服务的客户端错误地认为该服务不再可用,即使其 JVM 正在执行 GC。这可能会导致启动新的服务实例,从而在不必要的情况下进一步影响性能。 

虽然其他算法(例如并发标记清除 (CMS) 和垃圾优先 (G1))会在应用程序线程运行时执行部分工作,但其他部分仍需要暂停应用程序线程。如果这两种算法无法满足应用程序的内存需求,它们将回退到完整压缩收集周期。这种完整压缩需要应用程序暂停时间,暂停时间与堆大小成正比。 

与需要大量命令行选项且难以正确配置的传统垃圾收集器不同,C4 只需设置堆大小即可。C4 的堆空间可从 1Gb 扩展到 20Tb,且不会增加应用程序的暂停时间。

总结

软件开发正在转向云端,并使用微服务以灵活、
可扩展的方式实现这一目标。Java 作为全球最流行的编程语言,
是开发微服务的不二之选。相比其他语言开发的微服务,
Java 微服务也具有诸多优势。Azul Platform Prime 包含一个现代 JVM,该 JVM 对于垃圾收集和部分 JIT 编译系统采用了不同的内部算法。结合 ReadyNow 技术,Azul Platform Prime 可以有效消除微服务性能的预热阶段,使其成为在微服务上进行现代 Java 应用程序开发的理想选择。

Teal CTA Band

微服务和 Java