Quarkus雲原生框架入門體驗


背景描述

Java並不是為了Web而誕生,但似乎B/S架構讓Java生機無限,Spring全家桶的助推也使得Java在Web更為強大,微服務體系Spring Cloud更是順風順水,不得不說的Spring應用的痛點就是啟動過慢,內存占用偏高,對服務器資源占用較大,而且JVM的本身就難逃離內存的過度依賴。

隨着容器化技術Docker、Kubernetes,讓雲原生似乎成為了未來的發展方向,雲原生(Cloud-Native)這個概念最早由Pivotal公司的Matt Stine於2013年首次提出,提到雲原生首先想到的關鍵詞可能就是容器化、微服務、Lambda,服務網格等,當然這些是必要元素,但是不代表擁有這些元素就是雲原生應用,很多應用的部署只能說是基於雲來完成,比如私有雲、公有雲,這也是未來的趨勢。雲原生本質上不是部署,而是以什么方式來構建應用,雲原生的最終目的是為了提高開發效率,提升業務敏捷度、擴容性、可用性、資源利用率,降低成本。

Go語言作為一種雲原生語言也體現出了強大的生命力,Java也在變化,2018年Java出現了大量輕量級微服務框架,來面對未來的雲原生趨勢,Red Hat推出的Quarkus、Oracle的Helidon以及Spring Native都在快速發展,擁抱雲原生。

什么是Quarkus

傳統的Java技術棧是為了單機應用設計的,需要耗費大量的內存和CPU,並且啟動速度慢,當然也不存在雲、容器、k8s等技術體系,在雲原生時代,Java技術需要一個變革去迎接這個新世界的挑戰。
Quarkus就是為了應對雲原生而設計,並且是針對HotSpot、GraalVM定制化的k8s native框架,目標是使 Java 成為 Kubernetes 和serverless環境中的領先平台,同時為開發人員提供一個框架來解決更廣泛的分布式應用程序架構。這也反應在官方對Quarkus的介紹Supersonic Subatomic Java。

Quarkus有哪些特殊的地方,官方列舉了以下特點。
image.png
這些特點里面最過於吸引人的應該就是:

Quarkus提供了k8s擴展支持,可以讓開發人員不用關心k8s底層復雜的框架設計,也可以很好的使用其進行應用部署。此外還提供了native鏡像的能力,大大提高了應用的啟動時間和減小了內存的占用,同時對命令式和反應式編程的支持,也加大了容錯性,反應式可以提高應用對大量請求的響應速度。
quarkus_metrics_graphic_bootmem_wide.png

官方文檔:CREATING YOUR FIRST APPLICATION

准備

使用Quarkus需要准備環境

  • GraalVM Open-JDK-11
  • Maven 3.8.1+
  • Quarkus 2.6.2.Final

更多內容查看文檔:https://quarkus.io/guides/getting-started

下載

Quarkus官方網址:https://quarkus.io
GraalVM下載地址:

下載時候注意Graal有基於不同JDK版本的支持,如JDK8,JDK11,JDK11,可根據自己需求下載。

環境變量

配置環境變量方式同Java一樣

  • 配置JAVA_HOME或者GRAALVM_HOME C:\Program Files\Java\graalvm-ce-java8-21.0.0.2
  • 環境變量配置不生效解決方案:

多個JDK版本情況下出現環境變量切換不生效,Windows本身系統system32里面的環境變量加載等級要優先於用戶設置的環境變量,其根本原因是%JAVA_HOME%在path中配置的位置在%SystemRoot%\system32;后面,放到path最前面就好了。
image.png

  • 配置好后執行命令確保使用的是GraalVM
C:\Users\starsray>java -version
openjdk version "1.8.0_282"
OpenJDK Runtime Environment (build 1.8.0_282-b07)
OpenJDK 64-Bit Server VM GraalVM CE 21.0.0.2 (build 25.282-b07-jvmci-21.0-b06, mixed mode)

創建Web應用

使用maven創建

使用maven來快速創建一個應用,創建的位置在當前命令執行終端所在的根目錄,項目名稱為getting-started。

  • Linux & MacOS
mvn io.quarkus.platform:quarkus-maven-plugin:2.6.2.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=getting-started \
    -DclassName="org.acme.getting.started.GreetingResource" \
    -Dpath="/hello"
  • Windows
    • cmd
    mvn io.quarkus.platform:quarkus-maven-plugin:2.6.2.Final:create -DprojectGroupId=org.acme -DprojectArtifactId=getting-started -DclassName="org.acme.getting.started.GreetingResource" -Dpath="/hello"
    
    • powershell
    mvn io.quarkus.platform:quarkus-maven-plugin:2.6.2.Final:create "-DprojectGroupId=org.acme" "-DprojectArtifactId=getting-started" "-DclassName=org.acme.getting.started.GreetingResource" "-Dpath=/hello"
    

使用web頁面創建

使用web頁面:訪問通過網站:https://code.quarkus.io,這種方法類似於spring.io提供的web頁面

使用IDEA創建

使用IDEA,這里不得不說IDEA的強大,其實也是去加載https://code.quarkus.ioweb資源。

image.png

運行應用

GraalVM運行

項目創建后通過IDEA打開加載相關依賴啟動項目,在項目根目錄下,執行命令啟動

./mvnw compile quarkus:dev:

或者

mvn quarkus:dev

成功啟動應用后,查看控制台輸出

Listening for transport dt_socket at address: 5005
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2022-01-20 14:38:01,336 INFO  [io.quarkus] (Quarkus Main Thread) getting-started 1.0.0-SNAPSHOT on JVM (powered by Quarkus 2.6.2.Final) started in 3.635s. 
Listening on: http://localhost:8080

2022-01-20 14:38:01,347 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2022-01-20 14:38:01,348 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy, smallrye-context-propagation, vertx]

通過地址訪問 http://localhost:8080,Quarkus提供了一個Dev UI。
image.png

如果配置文件引入了數據庫相關驅動,卻沒有指定可用的jdbc配置信息,啟動項目會要求有可用的docker環境,自動創建docker鏡像並運行,這也說明了quarkus對雲原生的良好支持。

/opt/develop/graalvm-ce-java11-21.3.0/bin/java -Dmaven.multiModuleProjectDirectory=/home/starsray -Dmaven.home=/opt/ideaIU-2021.2.3/plugins/maven/lib/maven3 -Dclassworlds.conf=/opt/ideaIU-2021.2.3/plugins/maven/lib/maven3/bin/m2.conf -javaagent:/opt/ideaIU-2021.2.3/lib/idea_rt.jar=43095:/opt/ideaIU-2021.2.3/bin -Dfile.encoding=UTF-8 -classpath /opt/ideaIU-2021.2.3/plugins/maven/lib/maven3/boot/plexus-classworlds.license:/opt/ideaIU-2021.2.3/plugins/maven/lib/maven3/boot/plexus-classworlds-2.6.0.jar org.codehaus.classworlds.Launcher -Didea.version=2021.2.3 -DskipTests=true quarkus:dev
[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------------< cn.starsray:quarkus-web >-----------------------
[INFO] Building quarkus-web 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- quarkus-maven-plugin:2.6.2.Final:dev (default-cli) @ quarkus-web ---
[INFO] Invoking org.apache.maven.plugins:maven-resources-plugin:2.6:resources) @ quarkus-web
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Invoking io.quarkus.platform:quarkus-maven-plugin:2.6.2.Final:generate-code) @ quarkus-web
[INFO] Invoking org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile) @ quarkus-web
[INFO] Nothing to compile - all classes are up to date
[INFO] Invoking org.apache.maven.plugins:maven-resources-plugin:2.6:testResources) @ quarkus-web
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/starsray/IdeaProjects/quarkus-web/quarkus-web/src/test/resources
[INFO] Invoking io.quarkus.platform:quarkus-maven-plugin:2.6.2.Final:generate-code-tests) @ quarkus-web
[INFO] Invoking org.apache.maven.plugins:maven-compiler-plugin:3.8.1:testCompile) @ quarkus-web
[INFO] Nothing to compile - all classes are up to date
Listening for transport dt_socket at address: 5005
Press [h] for more options>
Tests paused
Press [r] to resume testing, [h] for more options>
Press [r] to resume testing, [o] Toggle test output, [h] for more options>
2022-01-16 23:25:11,416 INFO  [org.tes.doc.DockerClientProviderStrategy] (build-37) Loaded org.testcontainers.dockerclient.UnixSocketClientProviderStrategy from ~/.testcontainers.properties, will try it first
2022-01-16 23:25:11,888 INFO  [org.tes.doc.DockerClientProviderStrategy] (build-37) Found Docker environment with local Unix socket (unix:///var/run/docker.sock)
2022-01-16 23:25:11,890 INFO  [org.tes.DockerClientFactory] (build-37) Docker host IP address is localhost
2022-01-16 23:25:11,935 INFO  [org.tes.DockerClientFactory] (build-37) Connected to docker: 
  Server Version: 19.03.8
  API Version: 1.40
  Operating System: UnionTech OS Desktop 20 Home
  Total Memory: 15688 MB
2022-01-16 23:25:11,938 INFO  [org.tes.uti.ImageNameSubstitutor] (build-37) Image name substitution will be performed by: DefaultImageNameSubstitutor (composite of 'ConfigurationFileImageNameSubstitutor' and 'PrefixingImageNameSubstitutor')
2022-01-16 23:25:12,005 INFO  [org.tes.uti.RegistryAuthLocator] (build-37) Failure when attempting to lookup auth config. Please ignore if you don't have images in an authenticated registry. Details: (dockerImageName: testcontainers/ryuk:0.3.3, configFile: /home/starsray/.docker/config.json. Falling back to docker-java default behaviour. Exception message: /home/starsray/.docker/config.json (沒有那個文件或目錄)
2022-01-16 23:25:12,676 INFO  [org.tes.DockerClientFactory] (build-37) Ryuk started - will monitor and terminate Testcontainers containers on JVM exit
2022-01-16 23:25:12,676 INFO  [org.tes.DockerClientFactory] (build-37) Checking the system...
2022-01-16 23:25:12,677 INFO  [org.tes.DockerClientFactory] (build-37) ✔︎ Docker server version should be at least 1.6.0
2022-01-16 23:25:12,755 INFO  [org.tes.DockerClientFactory] (build-37) ✔︎ Docker environment should have more than 2GB free disk space
2022-01-16 23:25:12,780 INFO  [🐳 .io/.0.24]] (build-37) Creating container for image: docker.io/mysql:8.0.24
2022-01-16 23:25:12,781 INFO  [org.tes.uti.RegistryAuthLocator] (build-37) Failure when attempting to lookup auth config. Please ignore if you don't have images in an authenticated registry. Details: (dockerImageName: docker.io/mysql:8.0.24, configFile: /home/starsray/.docker/config.json. Falling back to docker-java default behaviour. Exception message: /home/starsray/.docker/config.json (沒有那個文件或目錄)
2022-01-16 23:25:12,874 INFO  [🐳 .io/.0.24]] (build-37) Starting container with ID: ec24cbadb7cdd72ce95cbdcb01011173939705e4c7685c070074fa7c0fbb270e
2022-01-16 23:25:13,122 INFO  [🐳 .io/.0.24]] (build-37) Container docker.io/mysql:8.0.24 is starting: ec24cbadb7cdd72ce95cbdcb01011173939705e4c7685c070074fa7c0fbb270e
2022-01-16 23:25:13,131 INFO  [🐳 .io/.0.24]] (build-37) Waiting for database connection to become available at jdbc:mysql://localhost:32773/default using query 'SELECT 1'
2022-01-16 23:25:26,498 INFO  [🐳 .io/.0.24]] (build-37) Container is started (JDBC URL: jdbc:mysql://localhost:32773/default)
2022-01-16 23:25:26,499 INFO  [🐳 .io/.0.24]] (build-37) Container docker.io/mysql:8.0.24 started in PT13.732728S
2022-01-16 23:25:26,499 INFO  [io.qua.dev.mys.dep.MySQLDevServicesProcessor] (build-37) Dev Services for MySQL started.
2022-01-16 23:25:26,500 INFO  [io.qua.dat.dep.dev.DevServicesDatasourceProcessor] (build-37) Dev Services for the default datasource (mysql) started.
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2022-01-16 23:25:27,968 ERROR [io.qua.hib.orm.run.sch.SchemaManagementIntegrator] (Hibernate post-boot validation thread for <default>) Failed to validate Schema: Schema-validation: missing table [employee]
2022-01-16 23:25:27,979 ERROR [io.qua.hib.orm.run.sch.SchemaManagementIntegrator] (Hibernate post-boot validation thread for <default>) The following SQL may resolve the database issues, as generated by the Hibernate schema migration tool. WARNING: You must manually verify this SQL is correct, this is a best effort guess, do not copy/paste it without verifying that it does what you expect.

create table employee (emp_no integer not null, birth_date date not null, first_name varchar(14) not null, gender char(1) not null, hire_date date not null, last_name varchar(16) not null, primary key (emp_no)) engine=InnoDB;

2022-01-16 23:25:28,003 INFO  [io.quarkus] (Quarkus Main Thread) quarkus-web 1.0.0-SNAPSHOT on JVM (powered by Quarkus 2.6.2.Final) started in 17.707s. Listening on: http://localhost:8080
2022-01-16 23:25:28,006 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2022-01-16 23:25:28,006 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, config-yaml, hibernate-orm, hibernate-orm-panache, jdbc-mysql, mybatis, mybatis-plus, narayana-jta, reactive-mysql-client, rest-client, rest-client-jackson, resteasy, resteasy-jackson, smallrye-context-propagation, smallrye-openapi, swagger-ui, vertx]

可以看到在啟動過程中創建了Docker容器,並且互通兩個容器。

Native Image運行

這也是quarkus最強大的地方,通過GraalVM的可選組件native image可以將Java應用打包成原生鏡像,真正做到秒級啟動,解決了傳統spring啟動的痛點,當然也期待spring的下一代產品Spring Native

  • 使用命令安裝native支持
sudo gu install native-image
  • 打包項目
mvn package -Pnative -DskipTests
  • 啟動 做到了秒啟動。

image.png

注意事項

啟動編譯失敗

如果本地存在多個JDK版本,項目啟動編譯失敗,請確保Java和Maven的版本是否正確,正確配置后重啟IDEA。

  • java
$ java -version
openjdk version "11.0.13" 2021-10-19
OpenJDK Runtime Environment GraalVM CE 21.3.0 (build 11.0.13+7-jvmci-21.3-b05)
OpenJDK 64-Bit Server VM GraalVM CE 21.3.0 (build 11.0.13+7-jvmci-21.3-b05, mixed mode, sharing)
  • maven
$ mvn -v
Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537)
Maven home: D:\maven\apache-maven-3.8.4
Java version: 11.0.13, vendor: GraalVM Community, runtime: C:\Program Files\Java\graalvm-ce-java11-21.3.0
Default locale: zh_CN, platform encoding: GBK
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

windows native build

native image需要先打包native可執行文件,我這里使用的是Linux環境,所以打包沒什么問題,在Windows環境下打包始終不能成功,參考官方文檔也一直未能成功打包,可能跟我使用的Windows11環境有關,有興趣的小伙伴可以自己試試。

項目介紹

對比Spring應用,Quarkus應用在目錄結構上並沒有太大不同,主要是一些依賴實現的區別。Spring針對JavaEE規范有一套自己的實現,Quarkus整合了多個優秀的開源框架實現,類似於Spring Cloud對微服務組件的整合。Quarkus主要整合了以下常用組件,有興趣的可以單獨了解

在getting-started項目中主要了解以下三方面內容

  • pom文件
  • REST Resource
  • Bean

Quarkus項目目錄樹結構

├─.idea
├─.mvn
│  └─wrapper
├─src
│  ├─main
│  │  ├─docker
│  │  ├─java
│  │  │  └─org
│  │  │      └─acme
│  │  │          └─getting
│  │  │              └─started
│  │  └─resources
│  │      └─META-INF
│  │          └─resources
│  └─test
│      └─java
│          └─org
│              └─acme
│                  └─getting
│                      └─started
└─target
    └─classes
        └─META-INF
            └─resources

pom.xml

在pom.xml中默認依賴了Quarkus的BOM清單,在 中做了關於Quarkus的統一版本管理,類似於spring-boot-dependencies的功能。

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>${quarkus.platform.group-id}</groupId>
            <artifactId>quarkus-bom</artifactId>
            <version>${quarkus.platform.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

pom.xml中還添加了打包相關的插件,使用quarkus-maven-plugin來進行打包、啟動、native image等操作。

<build>
    <plugins>
        <plugin>
            <groupId>${quarkus.platform.group-id}</groupId>
            <artifactId>quarkus-maven-plugin</artifactId>
            <version>${quarkus-plugin.version}</version>
            <extensions>true</extensions>
            <executions>
                <execution>
                    <goals>
                        <goal>build</goal>
                        <goal>generate-code</goal>
                        <goal>generate-code-tests</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

其中quarkus-resteasy是REST應用的依賴

<dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-resteasy</artifactId>
</dependency>

其他依賴

  • quarkus-smallrye-openapi openapi實現引入支持swagger-ui
  • quarkus-mybatis/quarkus-mybatis-plus mybatis及mybatis-plus組件
  • quarkus-hibernate-orm-panache hibernate JPA實現
  • quarkus-config-yaml yaml語法配置文件相關依賴
  • quarkus-reactive-mysql-client mysql客戶端依賴
  • quarkus-jdbc-mysql mysql JDBC驅動依賴
  • quarkus-resteasy-jackson rest接口依賴

REST Resource

項目中默認生成了GreetingResource一個REST資源類

package org.acme.getting.started;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/hello")
public class GreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello RESTEasy";
    }
}

@Path、@GET、@Produces都是基於JAX-RS規范實現的注解。在Spring盛行的今天,JavaWeb方向國內幾乎人人都是Spring程序員,對於JavaEE的規范了解使用都不多。
JAX-RS是JAVA EE6引入的一個新技術,英文全稱為Java API for RESTful Web Services,它的核心概念是面向資源,即Resource形式,常見的實現有Jersey、RESTEasy,就好比JPA的實現Hibernate。
關於JAX-RS參考:https://cloud.tencent.com/developer/article/1600752

Bean

Spring核心的功能就是IoC和DI,DI是指依賴注入,Quarkus中依賴注入使用了ArC,ArC是JavaEE規范中的CDI的一個實現,是Quarkus定制化的依賴注入組件。
CDI全稱Contexts and Dependency Injection,即上下文依賴注入,JEE眾多規范中的一個,從JavaEE開始CDI成為正式規范。CDI參考文檔:

Quarkus CDI參考文檔:https://quarkus.io/guides/cdi
通過CDI來初始化一個被容器管理的Bean時候都會指定Bean的生命周期,在Spring中指定的Bean默認都是單例的,生命周期通過@Scope注解來修改。
CDI提供了提供了一系列注解,根據注解名稱就能看出修飾Bean生命周期。

  • @ApplicationScoped
  • @SessionScoped
  • @RequestScoped
  • @ConversationScoped

在Quarkus中不需要指定創建Application級別的類,一般也不需要特定標注Bean的生命周期。

With Quarkus, there is no need to create an Application class. It’s supported, but not required. In addition, only one instance of the resource is created and not one per request. You can configure this using the different *Scoped annotations (ApplicationScoped, RequestScoped, etc)

Quarkus同樣支持類似於Spring中Controller注入Service的形式,新增一個GreetingService類

package org.acme.getting.started;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class GreetingService {

    public String greeting(String name) {
        return "hello " + name;
    }

}

修改GreetResource類,並注入GreetingService類

package org.acme.getting.started;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.annotations.jaxrs.PathParam;

@Path("/hello")
public class GreetingResource {

    @Inject
    GreetingService service;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/greeting/{name}")
    public String greeting(@PathParam String name) {
        return service.greeting(name);
    }

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "hello";
    }
}

開發

Development Mode

通過quarkus:dev命令會使應用在開發模式啟動,可以對pom.xml、Java類、Resouce目錄下的變動進行熱加載,減少重啟,這大大加快了開發調試效率。
debug模式會默認監聽5005端口,如果想要在項目啟動前進入調試模式通過命令行指定 -Dsuspend,也可以通過-Ddebug=false跳過所有調試。

Testing

在生成的pom.xml文件中默認引入了test依賴和相關打包插件

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>${surefire-plugin.version}</version>
    <configuration>
       <systemPropertyVariables>
          <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
          <maven.home>${maven.home}</maven.home>
       </systemPropertyVariables>
    </configuration>
</plugin>

創建的項目中默認包含了一個測試類GreetingResourceTest

package org.acme.getting.started;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import java.util.UUID;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
public class GreetingResourceTest {

    @Test    
    public void testHelloEndpoint() {
        given()
          .when().get("/hello")
          .then()
             .statusCode(200)    
             .body(is("hello"));
    }

    @Test
    public void testGreetingEndpoint() {
        String uuid = UUID.randomUUID().toString();
        given()
          .pathParam("name", uuid)
          .when().get("/hello/greeting/{name}")
          .then()
            .statusCode(200)
            .body(is("hello " + uuid));
    }

}

類似於SpringBootRunner,Quarkus使用QuarkusTest runner來運行,校驗返回結果。

Package

項目打包可以使以下命令,打包后的可執行jar或者native-image在./target目錄。

  • ./mvnw package & mvn package
  • mvn package -Pnative -DskipTests(打包native)

其他

Quarkus也提供了類似於SpringBoot banner的功能。

  • 禁用banner
    • quarkus.banner.enabled=false (application.properties)
    • -Dquarkus.banner.enabled=false(Java System Property)
    • QUARKUS_BANNER_ENABLED=false (ENV)
  • 自定義banner,添加文件到src/main/resources目錄,在application.properties中指定文件路徑
    • quarkus.banner.path=name-of-file

Spring對比

注解相關說明:
Spring中有一套自己針對相關功能的注解實現,Quarkus也有自己的整合實現,列舉相關的對應關系。
DI --> CDI
Spring Web --> JAX-RS
Spring Data JPA --> Panache
Swagger/Knife --> OpenAPI

創建Reactive Web應用

Reactive(反應式)與Imperative(命令式)相對應, Reactive一般都關聯着back-pressure, monads, or event-driven architecture等詞匯,也往往表現出一些特點。

  • Responsive - they must respond in a timely fashion(及時響應)
  • Elastic - they adapt themselves to the fluctuating load(彈性伸縮)
  • Resilient - they handle failures gracefully(優雅的處理失敗)
  • Asynchronous message passing - the component of a reactive system interact using messages(消息交互)

更多內容:https://principles.reactive.foundation

Reactive 反應式並不是Java所特有,是一種應對高並發、快速響應編程的一系列原則,Spring 5提供了全新的Spring WebFlux框架,相比於傳統的Spring MVC,實現了Reactive Streams規范,不需要Servlet API的支持,創建基於事件循環執行模型的完全異步且非阻塞的應用程序。
Quarkus同樣提供了對Reactive編程的支持。這些原則在使用資源(CPU和內存)的同時更有效地處理比傳統方法更多的負載,同時也會優雅地反應失敗。Quarkus相比於Spring WebFlux的優勢在於你不用糾結如何選擇,可以在同一個應用程序中同時使用反應式和非反應式編程,不需要依賴第三方應用或者技術棧。

Quarkus可以作為反應式和非反應式應用的橋梁。

Quarkus如何實現反應式,借用官方描述的一張圖片,通過Eclipse Vert.x and Netty等構建了一個特有的引擎,來處理non-blocking I/O交互。Quarkus或者應用可以通過代碼來編排數據庫、消息隊列等I/O事件交互。
image

創建應用

同創建Quarkus Web應用類似,創建反應式應用只需要選擇相應的反應式組件即可。

  • Hibernate Reactive with Panache
  • RESTEasy Reactive
  • Reactive MySQL client

pom.xml對應的依賴

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-hibernate-reactive-panache</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-reactive-mysql-client</artifactId>
</dependency>

創建對應的實體類

package com.starsray.entity;

import io.quarkus.hibernate.reactive.panache.PanacheEntity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Lob;
import javax.persistence.Table;
import java.time.LocalDate;

@Entity
@Table(name = "employee")
public class Employee extends PanacheEntity {
    @Column(name = "emp_no", nullable = false)
    private Integer empNo;

    @Column(name = "birth_date", nullable = false)
    private LocalDate birthDate;

    @Column(name = "first_name", nullable = false, length = 14)
    private String firstName;

    @Column(name = "last_name", nullable = false, length = 16)
    private String lastName;

    @Lob
    @Column(name = "gender", nullable = false)
    private String gender;

    @Column(name = "hire_date", nullable = false)
    private LocalDate hireDate;

    public LocalDate getHireDate() {
        return hireDate;
    }

    public void setHireDate(LocalDate hireDate) {
        this.hireDate = hireDate;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public LocalDate getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(LocalDate birthDate) {
        this.birthDate = birthDate;
    }

    public Integer getEmpNo() {
        return empNo;
    }

    public void setEmpNo(Integer empNo) {
        this.empNo = empNo;
    }
}

創建實體類對應的Resource,里面包含一個Reactive API和普通API。

package com.starsray;

import com.starsray.entity.Employee;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.smallrye.mutiny.Uni;

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.List;

@ApplicationScoped
@Path("employee")
public class EmployeeReactiveResource {


    @GET
    @Path("/{empNo}")
    @Produces
    public Uni<List<Employee>> get(@PathParam("empNo")int empNo) {
        return Employee.list("id", 1L);
    }

    @POST
    @Path("create")
    @Produces
    @Consumes
    public Uni<Response> create(Employee Employee) {
        return Panache.<Employee>withTransaction(Employee::persist)
                .onItem()
                .transform(inserted -> Response.created(URI.create("/Employees/" + inserted.id))
                        .build());
    }


    @GET
    @Path("/{id}")
    @Produces
    public Uni<Employee> getSingle(@PathParam("id") Long id) {
        return Employee.findById(id);
    }
}

配置數據庫連接

quarkus.datasource.db-kind=mysql
quarkus.datasource.username=root
quarkus.datasource.password=root
quarkus.datasource.reactive.url=mysql://localhost:3306/employees
quarkus.hibernate-orm.log.format-sql=true
quarkus.hibernate-orm.log.sql=true

啟動項目

./mvnw quarkus:dev

在體驗上幾乎看不出來區別。Quarkus底層都已經實現了。

Reactive

由於Reactive通過一種異步非阻塞式I/O和數據庫進行交互,因此需要一種異步HTTP實現結構體,Quarkus使用Mutiny 作為其核心的反應式編程模塊,因此HTTP請求支持返回兩種Mutiny類型(Uni and Multi),在引入的Hibernate Reactive with Panache模塊中只需要實體類繼承PanacheEntity類,就可以暴漏並使用這兩種返回類型。沒有特殊指定。RESTEasy Reactive會默認返回List對象為JSONArray。

  • GET

注意查看這一段代碼,返回類型是通過Uni來包裝的實體Value,沒有直接返回結果集。當數據庫讀取到相關數據后,Uni會獲取並返回結果。

@GET
@Path("/{empNo}")
@Produces
public Uni<List<Employee>> get(@PathParam("empNo") int empNo) {
    return Employee.list("id", 1L);
}

Uni<?>是一種異步返回類型,有點類似future,Uni作為一個占位符等待結果(暫稱為Item)返回,當接收到Mutiny分發的Item時,開發人員可以進行一些業務邏輯的處理,也可以表達為Reactive的延續性(相對於傳統命令式阻塞I/O的順序性),體現在獲取一個Uni,當Uni占位符得到分發的Item時,執行其他過程。

為什么返回類型是通過Uni來返回Item,通過關系型數據庫查詢,直接返回List 結果集並不是一種好的方式,傳統關系型數據庫並不能很好的處理流式結果,也並沒有相關設計協議。通過流來返回數據庫結果,需要保持連接或者開啟一個事務直到所有的查詢行被消費,如果有一個消費者消費的比較慢就會違背數據庫操作的黃金法則(不要保持一個太久的數據庫連接)。一般來說,過低的數據庫連接數和較長的數據庫連接會明顯的降低應用的並發性和效應效率,所以這里推薦使用Uni<?>來包裝返回結果,如果數據量過大,可以考慮使用分頁來解決問題。

  • POST

查看添加的POST請求代碼,根據JAX-RS的規范沒有提供一種類似於Spring @RequestBody的注解,默認會以JSON類型接收POST請求。

@POST
@Path("create")
public Uni<Response> create(Employee Employee) {
    return Panache.<Employee>withTransaction(Employee::persist)
        .onItem()
        .transform(inserted -> Response.created(URI.create("/Employees/" + inserted.id))
                   .build());
}

為了對數據庫進行寫操作,需要開啟一個數據庫事務,Panache. withTransaction會異步的去獲取一個事務,當接收到transaction時會調用persist方法,同樣persist會返回一個異步Uni結果,Uni會分發Employee在數據庫的插入結果,當插入動作完成時(我們編碼的延續),我們創建一個201 CREATED響應。在等待接收事務的過程中,RESTEasy Reactive會自動的讀取請求體為JSON並且創建一個Employee實體對象。

命令式與反應式

參考官方文檔上面的例子簡單的示例了反應式編程,在使用上幾乎和傳統命令式編程沒有區別,你可能會疑惑反應式編程和命令式編程有哪些不同或者有哪些好處。為了更好的理解和對比,我們需要先了解反應式和命令式在執行模型上的不同,理解執行模型的是理解Reactive的必要前提。

  • blocking I/O

在傳統的命令式編程中,依賴阻塞式I/O模型,框架會分配一個線程去處理一個請求,請求的整個過程都在這個線程上進行,這種模型非常不適合大規模擴展,為了處理大量的請求就需要大量的線程,應用的並發性能會受限於工作線程的數量,當需要與遠程服務進行交互調用時候,這些線程就會被阻塞。並且每個線程都映射到 OS 線程,因此在內存和 CPU 方面都有成本,大大降低了資源的利用率。
blocking-threads.png
blocking I/O

  • non-blocking I/O

反應式編程依賴非阻塞式I/O並且使用不同的執行模型,non-blocking I/O提供了一種高效的方式來處理並發I/O,很小一部分I/O線程可以處理大量的並發I/O,通過這種模型,處理請求不會委托給工作線程,而是直接使用這些 I/O 線程。這種模型節省了內存和 CPU,因為不需要創建工作線程來處理請求,提高了並發性,並且消除了對線程數量的限制,由於減少了線程切換的數量,它還提高了響應時間。
reactive-thread.png

  • 順序性到延續性

命令式體現在順序性,反應式體現在延續性,是兩種本質風格的轉變,二者在模型上最大的差別體現在,反應式編程的請求是由I/O線程來處理的,少量的線程即可處理大量的並發請求。
延續性風格編碼在處理請求過程需要與遠程服務(如 HTTP API 或數據庫)交互時,它不會阻塞執行等待響應結果返回,相反,它會調度 I/O 操作並延續處理請求的剩余代碼。 這種延續可以作為回調(使用 I/O 結果調用的函數)傳遞,或者使用更高級的結構體,例如反應式編程或協程。 不管延續如何表達,重要的是對I/O 線程的釋放,因此該線程可用於處理另一個請求。 當調度的 I/O 完成時,I/O 線程執行繼續,並且繼續處理掛起的請求。
因此,與 I/O 阻塞執行的命令式模型不同,反應式切換到基於延續的設計,其中 I/O 線程被釋放,並在 I/O 完成時調用延續。 因此,I/O 線程可以處理多個並發請求,從而提高應用程序的整體並發性。

Quarkus提供了不同的反應式編程庫:

  • Mutiny - an intuitive and event-driven reactive programming library
  • Kotlin co-routines - a way to write asynchronous code in a sequential manner

參考文檔:

更多資料:

源碼

https://gitee.com/starsray/quarkus


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM