GRAALVM:在Java世界中安全运行C/C ++应用程序
#云 #java #graalvm

作为Adyen移动面向付款团队中的C ++应用工程师,我们不断探索新技术,这可以使我们的付款应用程序更加可靠,更安全。在本文中,我们将讨论我们评估,采用和使用Graalvm 将我们现有的基于终端的付款应用迁移到云中的旅程。

让我们首先从遇到的技术挑战的背景信息开始,然后我们将遵循对GRAALVM功能的分析以及为什么选择它。最后,我们将总结有关使用GRAALVM的优势和缺点的发现。

当前情况:技术背景

付款应用程序已经用C/C ++编写,并且在各种类型的终端中广泛使用了多年,因为典型的嵌入式系统具有有限的硬件资源,并且需要在应用程序和操作系统之间有效的低级互动。


一切正常,直到我们开始考虑在云中运行C/C ++支付应用程序的可能性: Adyen 的Java世界。毫无疑问,当将应用程序从嵌入式设备迁移到云时,将需要一些设计范式,例如更多异步通信,多线程,非封锁调用等,以使应用程序更适合新环境。

除了对应用程序重新设计的考虑外,重要的问题是:我们是否重写应用程序或重复使用我们手头的代码?

重写代码

在Java中从头开始重写付款申请可能是一个有效的选择。从长远来看 但是,开发新应用程序需要大量的努力,尤其是在重新设计了多年来开发和运行的复杂应用程序时。现有的付款申请已经进行了一千多个迭代,并已在全球大规模的现场进行了验证。我们需要多少时间和精力才能重新实现相同的功能(付款,退款,订购等),并达到与现有应用的相同级别的可扩展性和鲁棒性?

重复现有代码

相反,重复使用现有工作代码的想法自然而然。它通过拥有大多数付款逻辑来加快开发周期的速度,因此我们可以努力处理物理终端和云环境之间的差异。但是,我们现在面临的挑战是在Java应用程序旁边安全地运行C ++代码。

在本机过程中运行C/C ++代码时,完全可能通过访问非法内存来分割故障。为了克服此类问题,将需要过程管理,如果发生崩溃时,它不仅可以重新启动新过程,还可以将请求重定向到活动过程。此外,应定义本机过程和Java应用程序之间的接口,并应考虑延迟成本,尽管实现依赖于实施。

我们已经查看了JNI(Java本机界面),这些界面很好地支持Java代码和其他语言之间的通信,例如C/C ++。

但是,C/C ++中的潜在内存问题可能导致JVM中的崩溃。这对付款申请的意义是将影响数千美元的付款。为了确保高质量和强大的支付服务,任何崩溃都将是至关重要的,应该首先避免。

最后,我们确定 graalvm ee可以帮助我们将现有的C/C ++应用程序集成到Java 生态系统中,无缝地与其他内存安全性和可接受的性能。

什么是graalvm?

GraalVM是一种Java虚拟机,主要在Java中实现,并支持其他编程语言解释,例如Python,JavaScript和编程语言,可以转换为LLVM(低级虚拟机)中间代码(BITCODE)。它旨在提供更自然的Java与其他语言之间接口的方式。我们将使用的企业版(EE)为传统上直接编译为本机代码的那些语言提供了托管内存。

GraalVM tech stack

增加记忆力保护

GRAALVM EE通过监视内存分配和交易来提供更好的安全性。通过这样做,非法内存访问成为一个抛出的例外,而不是分段故障。这使应用程序更加可靠,而不必担心运行时的未手持内存问题。

此外,GRAALVM EE创建了一个额外的层,将所有系统调用重定向到相应的Java接口,而不是直接执行系统调用。通过这种方式,当添加额外的安全性作为graalvm中的系统调用时,应用程序及其功能不受影响。

局限性和挑战

graalvm是相当新的技术,它仍在发展。 GRAALVM托管模式提供了所需的内存保护和额外的安全性,但它也带来了自己的局限性。

首先,应用程序使用的所有库都必须由GRAALVM LLVM工具链预先编译为LLVM比特码。这使GRAALVM功能可以检查所有执行和内存访问,例如在沙盒环境中执行代码。此外,将不允许调用本机代码和访问本机内存。由于访问无效,将引发运行时例外。

但是,记忆保护和预防呼叫本机代码的价格是有代价的。它增加了编译时间以及建筑过程的复杂性。例如,许多系统库,例如OpenSSL,Zlib等,无法直接链接。所有这些都必须使用GRAALVM LLVM工具链进行重新编译,这加大了编译时间并增加了维护每个库的建筑配置的努力。

此外,预先编译的“ Musl Libc”标准C库配备了LLVM工具链,如前所述,该工具链在Java中实现了系统调用的重定向。从应用程序的角度来看,执行系统调用应在有或没有GRAALVM的情况下使用相同。但是,由于GRAALVM是一项新技术,因此对于我们的多线程应用程序中系统调用的意外行为仍然存在某些异常。

幸运的是,这些问题已报告给GRAALVM,并在相应的Java代码中迅速解决。因此,当某些系统调用不支持或仅在GRAALVM中部分支持时,可能需要进行额外的测试和故障排除。

除此之外,在执行速度和内存使用方面存在与绩效有关的问题,我们将在下一节中讨论更多详细信息。

性能影响

GRAALVM编译器是一个JIT(即时)编译器,可在运行时编译代码的热零件,并不断优化代码,直到性能与本机机器代码一样好。但是,这意味着我们不会在应用程序的启动时获得最佳性能。因此,需要额外的热身才能实现更好的性能。为了知道多少热身是合适的,我们可以使用分析JSON解析器的结果来说明基本流程。

绩效分析:简单的JSON解析器

分析的目的是执行JSON Parser函数背对背超过1000次迭代,并比较GraalVM和本机执行之间的性能。

在我们的基准测试期间,我们定义了与实际付款期间使用的JSON有效载荷相同的JSON有效载荷,其重要目标是将场景接近实时系统。

分析结果如下图所示:

JSON parser profiling<br>

首次观察是,JSON解析器的性能由JIT编译器不断优化,直到达到稳定的性能为止。我们使用编译器选项-O0(无优化)进行本机汇编,而GRAALVM官方文档建议的GRAALVM使用-O1(基本优化)。但是,本地执行仍然优于执行GRAALVM。 GRAALVM的额外开销可以由不同的组件(例如其他内存保护,JIT编译器在运行时进行的优化)以及比特码解释器的速度进行贡献。

也可以提及,我们还使用汇编标志-O1和-O2对本机代码进行了相同的分析,这可以进一步改善执行时间。但是,GRAALVM的性能(请参阅稳定的红线)是可以接受的,并且在沙盒原型环境中运行时不一定会完全优化。如果需要,我们将在以下迭代中监视和优化性能。

第二观察提供了确定应用启动时正确热身的见解。该结果为我们提供了有用的信息,了解如果我们旨在实现80%的性能提高,则累积大约100次迭代的执行时间(执行时间从0.5s降至0.1s)。

)。

理想情况下,上述JSON解析器的分析流可以应用于整个应用程序的分析。但是,实际应用程序更复杂,并且具有不同的执行流。如果我们仅在热身过程中对一个特定流量进行优化,则在执行另一个流程时可能不会获得改进的性能。

幸运的是,在我们的情况下,这些不同的流量共享大多数常见功能,因此我们没有看到太大的性能影响,因为在热身后执行其他流动。如果不是这种情况,则需要考虑不同流量的热身。

内存使用情况

为了监视运行时的内存使用情况,GRAALVM提供了一个称为VisualVM的功能强大的工具,该工具可以监视系统指标,例如CPU使用,内存使用情况等。我们在同一JSON PARSER示例上进行了两个实验。

对于第一个实验我们测量了单个JSON解析器的堆使用。这给出了基线在Graalvm中运行简单的JSON解析器需要多少内存。我们可以看到最大堆的用法约为1.1GB,如下所示。这包括JSON解析器应用程序,GRAALVM和引擎的内存使用量,即位代码的解释器。

对于第二实验,我们测量了2个在2个单独线程中运行的JSON解析器的堆。值得一提的是,引擎是在线程之间共享的,这些线程可以减少线程数时减少一些堆的用法。

在2个线程中运行JSON解析器时的最大用法约为1.2GB。我们还使用4个线程运行实验,但是与2个线程相比,我们没有看到明显的差异。

Heap allocation

我们得出的结论是,内存使用不是在服务器上运行C/C ++应用程序的瓶颈,尽管与在嵌入式设备中本地运行应用程序相比,内存使用量仍然很高。在缩放线程数时,GRAALVM在基线的测量或测量值中使用了更多内存。但是,服务器通常具有更多能够处理此数量内存使用的内存资源。

结论

毫无疑问,Graalvm是相当新的技术,我们是试点用户,在云中生产中使用 c/c ++应用程序来利用GRAALVM。我们从这一旅程中学到了很多东西:我们的分析表明,使用GRAALVM运行应用程序的性能不如其他本机选择(例如JNI或具有Java Process Management的本机应用程序)的好。此外,我们可以看到为了在申请书的第一个请求中获得可接受的性能,需要进行热身。

另一方面,GRAALVM缓解了基本稳定性问题,即在JAVA旁边运行C/C ++应用程序时,我们认为更重要。 Graalvm为我们提供的是一个沙盒环境,使我们能够快速原型和测试现有应用程序,并将其适应到满足我们安全要求的新环境。

最终采用GRAALVM并利用现有的C ++代码基础使我们能够保持开发速度并更快地运输产品:我们在有史以来的市场上是至关重要的,客户反馈的必要和连续交付的速度,必须进行。<<<<<<<< /p>

参考

Safe and sandboxed execution of native code
GraalVM Interaction with native code and managed execution mode
VisualVM