1概述
SpringBoot框架不用多介绍,Java程序员想必都知道。相对来说熟悉Quarkus的人可能会少一些。Quarkus首页放出的标语:超音速亚原子的Java(Supersonic Subatomic Java)。
它是为 OpenJDK HotSpot 和 GraalVM 量身定制的 Kubernetes Native Java 框架,基于同类最佳的 Java 库和标准制作而成。Quarkus 的到来为开发 Linux 容器和 kubernetes 原生 Java 微服务带来了一个创新平台。
在本文中,我们将对这两个 Java 框架 Spring Boot 和 Quarkus 进行简单的比较。我们可以更好地了解它们之间的异同,以及一些特殊性。我们还会执行一些测试来衡量它们的性能。最后,我们会介绍一个开发人员如何从Spring转换到Quarkus。
2SpringBoot
Spring Boot 是一个基于 Java 的框架,专注于企业应用。它可以简单使用所有 Spring 项目,并集成了许多开箱即用的功能,来帮助开发人员提高生产力。
Spring Boot减少了配置和样板代码的数量。此外,由于其约定优于配置方法,它根据依赖项自动注册默认配置,大大缩短了 Java 应用程序的开发周期。
3Quarkus
Quarkus 是另一个采用与上述 Spring Boot 类似方法的框架,但还有一个额外的优点,即以更快的启动时间、更好的资源利用率和效率交付更小的工件(Supersonic、Subatomic)。
它针对云、无服务器和容器化环境进行了优化。尽管侧重点略有不同, Quarkus 也能与最流行的 Java 框架很好地集成。
4比较
如上所述,这两个框架都与其他项目和框架有很好的集成。但是,它们的内部实现和架构是不同的。例如,Spring Boot 提供两种类型的 Web 功能:阻塞(Servlets)和非阻塞(WebFlux)。
另一方面,Quarkus 也提供这两种方法,但与 Spring Boot 不同的是,它允许我们同时使用阻塞和非阻塞方法。此外,Quarkus 在其架构中嵌入了反应式编程方法。
为了在我们的比较中获得更准确的数据,我们将使用两个完全响应式的应用程序,这些应用程序使用 Spring WebFlux 和 Quarkus 响应式功能实现。
此外,Quarkus 项目中最重要的功能之一是能够创建原生镜像(Native Images,基于特定平台的可执行二进制文件)。因此,我们还将在比较中包含两个原生映像,但 Spring 的原生镜像支持仍处于试验阶段。另外我们需要用到 GraalVM。
测试应用
我们的应用程序将实现三个 API:一个允许用户创建邮政编码,另一个用于查找特定邮政编码的信息,最后按城市查询邮政编码。这些 API 是使用了前面提到的 Spring Boot 和 Quarkus 的反应式方法实现的,数据库使用的是PostgreSQL。
我们的目标是创建一个比 HelloWorld 程序稍微复杂一些的样例程序。当然,数据库驱动和序列化框架等内容的实现会影响我们的比较结果。但是,大多数应用程序可能都会需要处理这些事情。
因此,比较的目的并不是为了证明哪个框架更好或更高效,而是分析研究这些特定实现的一个案例。
测试计划
为了测试这两种实现,我们将使用 JMeter 执行测试,并分析其测试报告。此外,我们将使用 VisualVM 在执行测试期间监控应用程序的资源利用率。
测试将运行 5 分钟,会调用所有 API,从预热期开始,然后增加并发用户数,直到达到 1,500。我们将在前几秒钟开始填充数据库,然后开始查询,如下所示:
所有测试均在以下规格的机器上进行:
由于缺乏与其他后台进程的隔离,最终结果可能不太理想,但正如前面提到的,我们无意对这两个框架的性能进行广泛而详细的分析。
5调查结果
对开发人员来说,这两个项目的体验都很棒,但值得一提的是 Spring Boot 有更好的文档,在网上也可以找到更多资料。Quarkus 在这方面正在改进,但仍然有点落后。
在指标方面,我们有如下结果:
通过这个实验,我们可以观察到 Quarkus 在 JVM 和原生版本的启动时间方面几乎比 Spring Boot 快一倍。构建时间也快得多。在原生镜像的情况下,构建耗时:9 分钟(Quarkus)对 13 分钟(Spring Boot),JVM 构建耗时:20 秒(Quarkus)对 39 秒(Spring Boot)。
Artifact(工件)的大小出现了同样的情况,Quarkus 生成了更小的工件而再次领先。原生映像:75MB (Quarkus) 对 109MB (Spring Boot),以及JVM 版本:4KB (Quarkus) 对 26MB (Spring Boot)。
关于其他指标,结论并不是那么显而易见。因此,我们需要更深入地了解一下。
CPU
我们看到 JVM 版本在预热阶段开始时消耗更多的 CPU。之后CPU使用率趋于稳定,所有版本的消耗相对均等。
以下是 JVM 和 Native 版本中 Quarkus 的 CPU 消耗:
JVM 版的 Quarkus ↑↑↑
Native 版的 Quarkus ↑↑↑
内存
内存就更复杂了。首先,很明显,两个框架的 JVM 版本都为Heap(堆)预留了更多内存。尽管如此,Quarkus 从一开始就预留了较少的内存,启动期间的内存利用率也是如此。
然后,查看测试期间的利用率,我们可以观察到Native版本似乎不像 JVM 版本那样有效或频繁地回收内存。可以通过调整一些参数来改善这一点,在这个比较中,我们使用了默认参数,并没有对 GC、JVM 选项或任何其他参数进行更改。
让我们看一下内存使用图:
Spring Boot JVM ↑↑↑
Quarkus JVM ↑↑↑
Spring Boot 原生 ↑↑↑
Quarkus 原生 ↑↑↑
在测试期间尽管Quarkus出现了更高的峰值,但确实消耗的内存资源更少。
响应时间
最后,关于响应时间和峰值使用的线程数,Spring Boot 似乎略微具有优势。它能够使用更少的线程处理相同的负载,同时还具有更好的响应时间。
Spring Boot Native 版本在这种情况下表现出更好的性能。但是让我们看看每个版本的响应时间分布:
尽管有更多异常值,但 Spring Boot JVM 版本随着时间的推移取得了最好的进展,这很可能是由于 JIT 编译器优化[1]。
Quarkus JVM ↑↑↑
Spring Boot 原生 ↑↑↑
Quarkus 原生 ↑↑↑
Quarkus 在低资源利用率方面表现出强大的实力。然而,至少在这个实验中,Spring Boot 在吞吐量和响应能力方面与Quarkus旗鼓相当。
这两个框架都能够处理所有请求而没有任何错误。不仅如此,他们的表现也十分相似,并没有太大的差距。
总而言之
考虑到所有因素,在实现 Java 应用程序时,这两个框架都是很好的选择。
Native程序速度快且资源消耗低,是无服务器、短期(short-living)应用和资源消耗敏感环境的绝佳选择。
另一方面,JVM 应用程序似乎有更多的开销,但随着时间的推移具有出色的稳定性和高吞吐量,非常适合健壮、长寿命的应用程序。
测试程序的代码和用于测试它们的脚本可以在 GitHub 上找到[2]。
6从 Spring 转换到 Quarkus
随着K8s的兴起,对原生应用支持良好的Quarkus框架也越来越受到关注很多开发人员在考虑从 Spring 转换到 Quarkus。然而,开发人员在开始评估新的框架时通常必须搁置他们现有的知识。
幸运的是, Quarkus 不一样,因为它是由一群在 Java 技术方面具有深厚专业知识的工程师创建的。这包括 Spring API 兼容性,创建Quarkus的工程师同时也是在 Red Hat Runtime 上为 Spring Boot 提供支持的工程师。
7我是 Spring 开发者,为什么要选Quarkus?
越来越明显的是,容器化,尤其是 Kubernetes,正在迫使人们重新评估 Java ,用于开发云原生应用程序。Kubernetes 是一种高度动态的共享基础设施。由于集群中托管的应用程序数量的增长以及对应用程序生命周期变化的响应能力的提高(例如重新部署和向上/向下扩展),基础设施的投入变得更加划算。
传统的 Java 云原生运行时在现有的栈上增加了新的分层,而没有真正重新考虑底层。这导致更大的内存消耗和更慢的启动时间,以至于现在很多公司为了从 Kubernetes 集群的大量投资中获得更多价值,愿意放弃他们深厚的 Java 专业知识,为 Go 和 Node.js 重新培养人才和开发工具。
传统云原生 Java 栈 ↑↑↑
这正是 Quarkus 解决的问题。Quarkus 针对内存使用率和快速启动时间进行了优化。与其他云原生 Java 栈相比,在 JVM 上运行的 Quarkus 应用可以在相同数量的RAM中提供近两倍的应用程序实例,并且当打包为原生二进制文件时,实例数量增加了 7 倍。
这不仅仅是使用 SubstrateVM[3](GraalVM 的一个特性)简单地编译为原生二进制文件。
Quarkus 专为 Kubernetes 的基础设施优化了传统的 “高度动态”框架,从而降低了内存利用率并加快了初始启动速度,结果是运行时效率的显着提高。这些经过优化且文档齐全的框架称为“扩展”,由同类最佳的标准 API 组成。
运行时效率 ↑↑↑
![17.jpg](/uploads/allimg/20240612/1-240612103132F8.jpg)
我司为什么要从 Spring Boot 迁移到 Quarkus?
以我们公司为例,我司的旧系统基于 Spring 和 Tomcat。当我们维护和部署时,这个传统的框架给我们带来了一些困扰,基于以下原因我们决定迁移到Quarkus:
内存和 CPU 消耗:对于正在执行的操作,Spring 和 Tomcat 框架在应用的主要目的之外使用了过多的资源。
预热时间:Spring 应用程序可能需要 10-20 秒的时间才能启动,之后应用程序才可以开始预热。
无用的代码:作为开发人员,我们都讨厌样板代码(boilerplate code)。
测试:Quarkus 让编写单元测试和集成测试变得非常容易。只需在那里打一个@QuarkusTest 注释,它实际上会启动整个应用程序以运行您的测试。
横向扩展(Scale-out) vs. 纵向扩展(Scale-up):每个应用程序越小(资源方面),我们可以添加的越多。在这里横向可扩展性胜出。
学习曲线:Quarkus 的在线文档非常简单易懂。
8Spring 开发者可以活用哪些现有知识?
Quarkus 的 Spring API 兼容性包括 Spring DI、Spring Web 和 Spring Data JPA。同时也在计划其他 Spring API,如 Spring Security 和 Spring Config。在 JVM 上运行时,Quarkus 应用程序几乎可以利用任何 Java 库。只要不使用 Java 反射,这些Java库就可以编译为原生。
例如,受 Spring 开发人员欢迎的 Lombok 库就可以原生编译。需要明确的是,Quarkus 中的 Spring API 兼容性并非为了作为一个完整的 Spring 平台来重新托管现有的 Spring 应用程序。目的是为了让基于 Quarkus 开发新应用程序成为一种自然的入门体验。结合预先优化的扩展,Quarkus 为微服务开发提供了大量的功能。很多开发人员已成功将 Spring 应用程序迁移到 Quarkus。
Spring 框架本质上是高度动态的。为了解决这个问题,Quarkus的Spring 兼容性扩展将 Spring API 映射到现有扩展中的 API,这些扩展已经针对快速启动、降低内存利用率和原生编译进行了优化,例如 RestEasy 和 CDI。此外,Quarkus的Spring 兼容性扩展不使用 Spring 应用程序上下文。由于这些原因,尝试使用额外的 Spring 库可能不会奏效。
Quarkus Spring Web Example
import java.util.List;
import java.util.Optional;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping('/person')
public class PersonController {
@GetMapping(path = '/greet/{id}', produces = 'text/plain')
public String greetPerson(@PathVariable(name = 'id') long id) {
String name='';
// ...
return name;
}
@GetMapping(produces = 'application/json')
public Iterable<Person> findAll() {
return personRepository.findAll();
}
Quarkus Spring Repository Example
package org.acme.springmp;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findByAge(int age);
}
Quarkus Spring Service + MicroProfile Fault Tolerance Example
import org.eclipse.microprofile.faulttolerance.Fallback;
import org.eclipse.microprofile.faulttolerance.Timeout;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service // Spring
public class PersonService {
@Autowired // Spring
@RestClient // MicroProfile
SalutationMicroProfileRestClient salutationRestClient;
@Value('${fallbackSalutation}') // Spring
String fallbackSalutation;
@CircuitBreaker(delay=5000, failureRatio=.5) // MicroProfile
@Fallback(fallbackMethod = 'salutationFallback')// MicroProfile
public String getSalutation() {
return salutationRestClient.getSalutation();
}
9对Spring开发者有额外的好处吗?
除了提高内存利用率和启动时间外,Quarkus 还为 Spring 开发人员提供了以下好处:
功能即服务 (FaaS) 。当编译为原生二进制文件时,Quarkus 应用程序可以在 0.0015 秒内启动,从而可以将现有的 Spring 和 Java API 知识与 FaaS 功能结合使用。(Azure、AWS Lambda)
实时编码。从“Hello World”示例应用程序开始,然后将其转换为复杂的微服务,而无需重新启动应用程序。只需保存并重新加载浏览器即可查看沿途的变化。Quarkus 实时编码“开箱即用”,与 IDE 无关。
支持反应式和命令式模型。 Quarkus 有一个反应式核心,支持传统的命令式模型、反应式模型,或在同一应用程序中同时支持两者。
早期检测依赖注入错误。 Quarkus 在编译期间而不是在运行时捕获依赖项注入错误。
最佳框架和标准的结合。Quarkus 在同一应用程序中支持 Spring API 兼容性、Eclipse Vert.x、MicroProfile(JAX-RS、CDI 等)、反应式流和消息传递等,可以在一个项目中同时使用 Spring 和 MicroProfile API。