截至2020年,Java仍然是构建Web应用程序的最流行编程语言之一,尽管它必须面对来自像Go、Python和TypeScript等新语言的激烈竞争。
在Java世界中,Spring框架已成为微服务开发的事实标准。通过像Spring Boot和Spring Data这样的库,该框架易于使用,允许高效且大部分无痛的开发。
然而,近年来出现了新的框架,声称可以改善Java应用程序的启动时间和内存占用。由于我目前正在使用Java开发一个基于微服务的大型应用程序,因此我想检查哪种Java框架最适合这样的架构。
因此,我的主要关注点将是开发的易用性以及生成的微服务的资源管理。
在资源管理方面,Spring(实际上大部分Java平台)从来没有拥有最好的声誉,特别是当涉及到单个进程所需的开销时。在应用程序服务器时代,这不是一个主要问题,因为实例数量很少。然而,随着微服务架构的兴起以及其大量的小实例,这越来越成为一个问题,正如Christian Lusardi最近所说:
“我发现一个基本的Java应用程序在Spring Boot上运行需要至少1GB的RAM,这在开发中间件应用程序时是可以接受的,但在微服务架构中这非常糟糕!”
候选人
Spring
Spring在2003年作为对早期Java Enterprise复杂性的回应而诞生。在其核心中,Spring作为依赖注入(DI)和面向切面编程(AOP)框架开始,并发展成易于使用的Web应用程序框架。通过其广泛的文档、广泛的使用和无数的库,Spring允许开发人员高效地创建和维护应用程序,并提供平缓的学习曲线。
Spring使用反射在运行时执行DI。因此,当启动Spring应用程序时,会扫描注释类的类路径。基于此,具体对象被实例化并链接。
虽然这非常灵活且开发人员友好,但会使启动变慢并且会占用大量内存。此外,将此机制迁移到GraalVM相当困难,因为它不支持反射。
Micronaut
Micronaut是由Grails Framework的创建者于2018年推出的现代全栈微服务框架。
它提供了构建全功能微服务应用程序所需的所有工具。同时,它旨在提供快速启动和减少内存占用。通过使用Java注释处理器在编译时而不是运行时执行DI、创建面向切面的代理和配置应用程序,可以实现这个目标。
Micronaut的许多API受Spring和Grails的启发。这是有意为之,有助于快速引入新开发人员。因此,Micronaut提供了诸如Micronaut HTTP、data、security和连接器等模块,用于各种其他技术。然而,这些库的成熟度仍然落后于其Spring的对应物。
Quarkus
Quarkus是由Red Hat在2019年推出的基于Kubernetes的Java框架。它建立在MicroProfile、Vert.x、Netty和Hibernate等标准之上。
Quarkus的目标是通过在编译时而不是构建时(在Quarkus中,这也称为“编译时启动”)使用自定义Maven插件尽可能地执行工作,从而使Java成为Kubernetes的领先平台,从而实现更快的启动、低内存占用和几乎即时的容器编排平台扩展。
Quarkus主要使用现有的标准技术,但它是可扩展的。然而,由于该项目仅在一年前启动,因此这些扩展的成熟度和兼容性并不总是明确的。随着该平台的发展,这种情况可能会发生改变。
Helidon MicroProfile
MicroProfile项目始于2016年,当时尚不清楚Oracle将如何继续在Java Enterprise上工作。
与其前身JEE一样,MicroProfile是一个可以由各种供应商实现的规范。
自那时以来,已经出现了多个这样的实现,最著名的是Payara Micro和Helidon MP。 Payara是一个基于GlassFish的Jakarte EE服务器,Payara Micro是其MicroProfile实现。 Helidon是Oracle在2018年启动的运行时,提供了自己的MicroProfile规范实现。
由于它们源自JEE,因此MicroProfile规范是成熟且有良好文档的。然而,现代技术的连接器缺失或替代Spring Data和Spring Security库的缺失。
此外,由于在此期间已经开始在Eclipse Foundation内部开发Jakarta EE,因此MicroProfile的未来不确定。因此,这两个项目似乎将来会合并或至少密切协调。
框架比较
为了比较上述框架,我使用了每个框架实现了一个简单的应用程序。示例应用程序包括用于创建、读取、更新和删除对象的REST接口和一个将这些对象存储到表中的关系数据库连接器。
如果框架支持访问不同的数据库方式,则我尝试为不同的变量实现示例项目。然后,我比较了这些应用程序的性能。
我使用OpenJDK Docker映像运行了此应用程序。如果框架支持生成本地GraalVM映像,则我还比较了这些映像的性能。此外,查看我的文章“使用R2DBC、Micronaut和GraalVM进行反应式数据库访问”以获取有关GraalVM的更多信息。所有这些应用程序的源代码都可以在Github上找到。我已经比较了这些应用程序在三个关键阶段的性能:
- 实现示例应用程序有多容易?为了实现这些框架,我必须查阅文档并在类似 Stack Overflow 的平台上搜索信息。
- 编译应用程序需要多长时间?我测量了执行干净构建所需的时间,包括生成 Docker 镜像的时间。对于 GraalVM,这包括生成本机镜像的时间。
- 启动应用程序需要多长时间?我在这里测量了从运行“docker up”到应用程序正确响应第一个 HTTP 请求所需的时间。此外,我还比较了启动后空闲应用程序的内存占用。
- 负载:应用程序能够处理多少请求数?我使用 JMeter 进行负载测试,并将应用程序测试为 25% 的请求执行数据库写入,75% 的请求仅执行数据库读取。然后,我再次测量了应用程序在其性能达到峰值时的内存占用。
我在运行 Ubuntu 19.01 的 Google Cloud Platform 虚拟机上执行了所有测试,该虚拟机配备了四个英特尔 Haswell CPU 和 15 GB 的内存。为了避免干扰因素,所有测量均重复了多次。你可以在 GitHub 上找到使用的脚本以及原始数据。
结果
开发的易用性
由于我只有使用 Spring Boot 的先前知识,因此这是一个有点不公平的比较。然而,在检查文档和可用的信息和示例时,Spring 显然是最容易入门的框架。
Micronaut 的文档编写得很好,它具有与 Spring 和 GrailVM 类似的 API,因此 Spring 开发人员可以轻松入门。
在我看来,Quarkus 的学习曲线有点陡峭,因为相对于 Spring 和 Micronaut,其库和 API 不够成熟。我特别缺少易用的数据库访问。
但是,在我看来,Helidon 明显是最后一个,因为我花了很多时间才让应用程序运行起来。
编译
使用 OpenJDK 时,所有框架的编译时间都相当类似,介于 6.98 秒(使用 JDBC 的 Spring)和 10.7 秒(Quarkus)之间。
然而,生成本机 GraalVM 映像的时间相当耗时,介于 231.2 秒(使用 JDBC 的 Micronaut)和 351.7 秒(使用 JPA 的 Micronaut)之间。这使得本机映像在开发中基本上是无用的,因为等待四分钟来编译一个简单的应用程序太多了。
启动
使用 Spring Data 的 Spring Boot 应用程序平均需要 8.16 秒才能启动。删除 JPA 和 Spring Data 后,此时间缩短为仅 5.8 秒。
在这里,Micronaut(使用 JPA 为 5.08 秒,使用 JDBC 为 3.8 秒)和 Quarkus(为 5.7 秒)保持了它们的承诺,即启动时间更短。
只有 Helidon MP 比 Spring 更慢,平均为 8.27 秒。
然而,真正的赢家是 GraalVM。本机映像的启动时间介于 1.39 秒(Quarkus)和 1.46 秒(使用 JDBC 的 Micronaut)之间,比 OpenJDK 实现快得多。
启动后的内存使用情况非常相似。Spring 分配了 420 MB 的内存(使用 Spring Data)和 261 MB(使用 JDBC)。
Micronaut 使用 JPA 和使用 JDBC 时分别为 262 MB 和 178 MB。
Quarkus 为 197 MB,性能要好得多。Helidon MP 的内存占用与 Spring Boot 相似,为 414 MB。
在这里,本机 GraalVM 映像比 OpenJDK 实现表现出色得多,仅使用了 7 MB(Quarkus)至 27 MB(使用 JPA 的 Micronaut)的内存。
峰值性能
在负载下,Spring Boot 的表现相当不错,能够提供 342(使用 Spring Data)和 216(JDBC)个请求/秒(r/s),并使用 581 MB(Spring Data)和 484 MB(JDBC)的内存。Helidon 明显排名最后,只能提供 175 个 r/s,同时分配了超过 1 GB 的内存。
其他框架能够提供 400 r/s(Quarkus 作为本机映像运行)至 197 r/s(Quarkus 在 OpenJDK 上运行)。各种 Micronaut 实现介于其中,JDBC 比 JPA 更具优势,本机映像比 OpenJDK 更具优势。
在内存使用方面,Quarkus 在 OpenJDK 上的表现令人惊讶,仅消耗 255 MB 的内存。这甚至比运行为本机映像的相同应用程序还要少,后者平均需要 368 MB 的内存。
然而,Micronaut 的内存使用效率相当低下。在 OpenJDK 中运行的 JPA 实现平均使用 880 MB 的内存,比 Spring 的内存使用量高出 50% 以上。然而,使用 JDBC 和本机映像有助于 Micronaut 将其内存占用减少到 367.8 MB。
结论
Simon Connellan 在 Unsplash 上的照片
新的 Java 框架 Micronaut 和 Quarkus 承诺相对于现有框架(如 Spring 和 MicroProfile)具有更快的启动时间和更低的内存占用。
它们确实实现了这个承诺,但只有在空闲或小负载下才能胜任。在这些情况下,它们比 Spring 表现更好,特别是与本机 GraalVM 映像相结合时。然而,在负载下,它们并没有提供太多优势,即使作为本机映像运行时也是如此。
而且,由于 Spring 仍然提供迄今为止最好的开发人员体验,因此在我看来,它仍然是适用于微服务应用程序的最佳 Java 框架,即使考虑到其启动性能较差。
对我来说令人惊讶的是使用 Hibernate/JPA/Spring Data 的巨大成本。即使对于这个非常简单的应用程序,内存(但也是 r/s)方面的开销也非常巨大。在这里,我特别喜欢 Micronaut Data 的解决方案,它可以自动生成不需要 JPA 的存储库代码。这确实是可以添加到 Spring Data 中的东西。
本机 GraalVM 映像在启动时的速度和内存使用效率非常快,但在负载下并没有提供明显的优势。由于本机 GraalVM 的生成会带来一些额外的困难,并且编译时间会大大增加,因此,如果需要快速启动(例如,在无服务器架构或需要快速扩展时),这种技术目前仅有用。在所有其他情况下,成本仍然比类似负载的性能要高得多。# 资源
评论(0)