导读:随着各种开放云平台的出现,传统的基于 Java 和 J2EE 的编程模型和框架在云环境下无法适应,高内存需求和启动速度缓慢等限制了它们在云平台的扩展能力,面向云原生的编程框架需求变得越来越多。2019 年红帽发布了基于云原生的 Java 框架 Quarkus,本文作者冯征从 Quarkus 项目背景、设计特色、应用场景、开发难点等方面做了全方位剖析,希望给关注 Quarkus 框架的开发者带来一些帮助和思考。他还将在 QCon 全球开发大会(北京站)2020 分享 Quarkus 的更多技术动态,敬请关注!
2018年,在一次内部会议中,有人问到“有什么会成为红帽中间件最大的威胁?”,红帽中间件副总裁 Mark Little 的回答让我印象很深。他最担心的事情是,如果一种新的编程语言(比如Go)能够给用户提供更快地运行速度,并且提供更丰富的类库来支持从 Java 应用的迁移,那么在云环境不断完善并进入用户的生产系统后,这种应用替换和迁移的代价在不断降低,使得用户会更倾向于使用新的编程语言来进行开发。而红帽所有基于 Java 的中间件产品都用新的编程语言来重构一遍几乎是不可能的。所以我们必须要让 Java 更快一些。
后来,红帽内部建立了一个“秘密”项目 Protean ,也就是 Quarkus 早期的名称。当时只有少数的开发人员参与设计并开发原型系统。早期测试的时候,我们在 Kubernetes 中单纯利用 Wildfly 或者 JBoss EAP 的 Docker 镜像来启动用户的应用,最多只能扩展到 100 多个实例,而且启动的时间很长。而利用 Quarkus 产生的 Native 应用 Docker 镜像,可以轻松地扩展到 1000 多个示例,是前者的 10 倍多,并且启动时间大大缩短。这些都促使红帽中间件在 Quarkus 的创新,使之成为红帽整个 Open Hybrid Cloud Strategy(开放混合云架构)中重要和不可或缺的一部分。
Quarkus 是基于 J2EE( Jakarta EE )和 MicroProfile 标准来作为技术栈,而 Spring 有自己的一套东西,大家可以从 https://simply-how.com/ Quarkus -vs-spring 来看两者的区别。其实 Spring 社区也开始在实践用 Graal VM 来构建 Native 应用,项目网站是https://github.com/spring-projects-experimental/spring-graal-native。我们可以看到,目前这个项目还是处于验证阶段,和 Quarkus 相比还有很远的距离。
从一开始, Quarkus 就围绕容器优先理念进行设计。通过以下方式针对低内存使用量和快速启动时间进行了优化:
这一直是 Quarkus 设计的重要组成部分。当把应用程序编译为本地映像时,它的启动速度会更快,并且可以用比标准 JVM 小得多的堆栈参数来运行。 Quarkus 已经通过了Substrate的所有测试,可以在没有 -H:+ ReportUnsupportedElementsAtRuntime 标志的情况下运行。
在构建阶段将进行尽可能多的处理,因此应用程序将仅包含运行时实际需要的类。在传统模型中,所有的类都会在应用程序初始化时进行处理,即使它们仅使用一次。而使用 Quarkus ,它们甚至都不会加载到运行时的 JVM 虚拟机中。 因为在构建阶段我们就尽量完成初始化的工作,这将减少应用在运行时的内存使用量,并缩短应用的启动时间。
Quarkus 尽可能避免使用Java的反射功能。
当使用 Native 原生应用运行时, Quarkus 在 Native 映像的构建过程中提前引导尽可能多的框架代码。这意味着生成的 Native 映像已经执行了大多数启动代码,并将结果序列化到最终的可执行映像文件中,从而使应用的启动速度更快。
Quarkus 的内核是围绕着 CDI 设计,整个核心就是一个微型的 CDI 容器,而且完全支持异步的编程模型比如 Netty 和 Vert.x ,可以支持直接利用 Kotlin 语言进行开发。整个 Quarkus 框架采用了 Extensions 进行扩展,其中包含了目前红帽中间件大部分的产品,比如 Hibernate ORM、 Artemis、Resteasy、Undertow、Narayana、 Infinispan、Camel、KeyCloak 等等。所以对用户来说,是可以轻松的利用这些框架和工具来进行开发。
从内部的 Quarkus 0.1 测试版本开始,到目前刚刚发布 1.4.1.Final , Quarkus 的开发速度是很快的,不断带来更多的 Extension 扩展来丰富功能。值得注意的是,从 Quarkus 1.4 版本开始,JDK 8 将被标记为过时,而从 1.6 版本开始, Quarkus 将不再支持 JDK 8,而会支持 JDK 11 的 LTS 版本。
Quarkus 主要应用场景是开发云原生应用,用户可以轻松利用 Quarkus 生成 Native 映像并进行部署。当然, Quarkus 不仅仅可以运行在 Kerbenetes 环境中,也可以运行在 JVM 虚拟机环境中,甚至可以运行在用户本地的 IDE 开发环境中。它解决的核心问题是加速 Java 程序的启动和运行速度以及更小的运行时内存占用。用户非常惊叹 Native 应用的启动速度,往往能比正常的 Java 应用快 10 倍以上。而且经过优化以后, Native 的映像文件可以做的很小,非常适合在云环境中使用。
我们来看一下内存使用和启动速度的比较:
其实 Quarkus 有很多的脚手架工具来帮助用户搭建开发环境,用户也可以从网站 code. quarkus .redhat.com 来轻松的生成项目工程。开发者利用 Maven 或者 Gradle 来构建应用也是非常的方便。如果要说最难的地方的话,可能会是开发 Quarkus 的 Extension 扩展。我目前的工作就是把 Camel 的组件加入到 Quarkus 中成为扩展,这样用户就可以方便的在 Quarkus 应用中使用这些 Camel 组件。
我会通过一个具体的示例来说明如何进行 Quarkus 的扩展开发。目前 Quarkus 还不支持 Scratch 方式来构建扩展项目,所以只能是在 Quarkus 的代码树中来新增扩展。比如我们想要加一个新的扩展 Upper-extension ,它可以提供应用调用的方法Upper.convert(String str)把字符串 str 全部转换成大写:
public class Upper {
public String convert(String str) {
return StringUtils.upperCase(str);
}
}
首先我们需要从 Github 下载或者复制 Quarkus 源代码:
git clone https://github.com/quarkusio/quarkus.git
cd quarkus
cd extensions
mvn io.quarkus:quarkus-maven-plugin:1.4.2.Final:create-extension -N \
-Dquarkus.artifactIdBase=upper \
-Dquarkus.artifactIdPrefix=quarkus- \
-Dquarkus.nameBase="Upper Extension"
生成的 Maven 工程包含两个子项目 Deployment 和 Runtime 。我们来分别看看这两个子项目,首先在 Deployment 中主要包含 Quarkus 在构建阶段所需要做的工作,带有 @BuildStep 注解标记的方法都会在构建阶段执行,比如在 UpperProcessor.java 文件中
@BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem(FEATURE);
}
这表明会返回一个包含当前扩展名称的BuildItem。
各个构建步骤之间可以通过 BuildItem 来传递信息,比如上面的 FeatureBuildItem 当有其它的构建步骤需要获得这个信息的时候可以作为参数来使用:
@BuildStep
void printFeatures(List<FeaturesBuildItem> features) {
foreach(FeatureBuildItem feature : features) {
System.out.println(feature.getInfo());
}
}
我们需要在 deployment/pom.xml 中使用如下代码处理这些 @BuildStep 注解标记的方法:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${quarkus.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
那么如何在构建阶段创建类并实现静态初始化操作呢 ?在这里我们就需要用到 Bytecode Recording(字节码记录)。
在 Runtime 子项目中新建一个 UpperRecorder.java :
@Recorder
public class UpperRecorder {
public void createUpper(BeanContainer container) {
Upper upper = new Upper();
container.instance(UpperProducer.class).setUpper(upper);
}
}
注意这里的 @Recorder 注解标记, Quarkus 会把构建阶段每次的方法调用都序列化,并通过生成 Bytecode 的方式保存下来。所以 CreateUpper 方法中的操作在构建阶段都不会立即执行,而是会转换成 Bytecode 并延迟到运行时再执行。
而在构建阶段,我们可以做的初始化工作,包括设置参数,扫描特定的 Annotation 并注册(我们在 Camel- Quarkus 中也使用了)等等。这样做的好处是在构建阶段可以尽量多的完成初始化工作,这样运行时启动应用的速度会大大加快。如果是要通过Graal VM生成Native映像的话,有些初始化工作是不能在构建阶段做的,比如监听网络端口,启动线程池等等。
回到 Deplolyment 的 UpperProcessor 中,我们增加一个构建步骤来创建 Upper :
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void create(UpperRecorder recorder, BeanContainerBuildItem beanContainer) {
recorder.createUpper(beanContainer.getValue());
}
这样我们基本完成了 Upper 扩展,为了让用户可以在 CDI 环境中使用 Upper,我们还需要在 Runtime 中新建 UpperProducer.java :
@Singleton
public class UpperProducer {
private volatile Upper upper;
void setUpper(Upper upper) {
this.upper = upper;
}
@Produces
Upper getUpper() {
return upper;
}
}
然后在 deployment/UpperProcessor.java 中加入:
@BuildStep
AdditionalBeanBuildItem upper() {
return AdditionalBeanBuildItem.unremovableOf(UpperProducer.class);
}
}
使得 Quarkus 把 UpperProducer 也当成 CDI Bean 来处理,可以在依赖注入中使用 Upper 。
最后用户就可以在应用中这样来使用:
@ApplicationScoped
public class MyService {
@Inject
Upper upper;
public String onMessage(String message) {
return upper.convert(message);
}
}
总的来说,在 Extension 的扩展中,我们使用 @BuildStep 来标记各种构建步骤,并且尽量在构建阶段完成初始化工作,这样可以减少通过 Graal VM 生成的可执行映像文件大小,加快应用的启动速度,缩短启动时间。
很多公司在早期的 Tech Pre 版本开始就在关注 Quarkus ,比如(Ericsson、Amadeus)。随着 Quarkus 正式 GA 产品的发布,我相信会有很多的公司加入到 Quarkus 中。红帽一直以来的都坚持采用“社区驱动”的产品开发模式,未来,红帽中间件的所有产品都计划考虑加入到 Quarkus 中,这样可以让用户之前基于 J2EE 开发的应用能够在 Quarkus 中进行测试。
云原生应用越来越重要,大量基于Java 开发的应用都面临着同样的问题,比如在云环境中如何快速的部署和提高应用启动速度。Quarkus是红帽开放混合云战略的重要组成部分,也是红帽中间件下一代的核心产品。作为云原生应用的开发利器,Quarkus 将帮助用户利用 Jakarta EE 和 MicroProfile 技术栈更快和更便利的开发产品,并快速部署到云环境中。作为 Java 开发者,需要对云原生应用有更多的了解,并对 Quarkus 这样的新框架熟悉掌握,最后也希望能够有更多的开发者关注 Quarkus 并积极参与到社区里。我们期待推出的产品是经过社区用户的大量反馈,并且也能真正适应开发者需求的。
作者介绍:
冯征,红帽 JBoss 中间件团队高级软件工程师。2009 年加入红帽,有 10 余年的开发经验,作为核心成员参与 JBossTM 事务管理器(Narayana)的开发。目前在 JBoss Fuse 团队做 Camel 项目的研发工作,在上游的开源项目(Narayana、WildFly、Camel、CXF、Camel-K、Camel- Quarkus )中也做过贡献。
领取专属 10元无门槛券
私享最新 技术干货