21

Quarkus:超音速亚原子 Java 体验

 4 years ago
source link: http://www.ibm.com/developerworks/cn/cloud/library/cl-lo-quarkus-supersonic-subatomic-java-experience/index.html?ca=drs-
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前言

在传统单体应用模式下,技术人员会对整个应用堆栈进行优化,从而让一个应用服务器上可以运行许多应用程序。例如,在一个 JBoss EAP 实例上,我们可以运行上百个应用程序。

传统单体应用架构大致分为五层:底层为操作系统;操作系统上运行 Java 虚拟机;Java 虚拟机之上运行应用服务器;在应用服务器上是应用开发框架,如 SpringBoot、MVC 等;在应用开发框架上是应用程序(如 war、jar 格式的应用包),如下图 1 所示:

图 1. 传统单体应用架构

fqimA32.png!web

fqimA32.png!web

随着 Kubernetes 和容器的发展,虽然不少应用已经实现了容器化运行,但 Java 堆栈并没有太大变化,如下图 2 所示。

图 2. Kubernetes 时代的 Java 应用架构

EJB7F3r.png!web

EJB7F3r.png!web

开发人员认为 Java 过重:启动时间慢、消耗内存大,不适合于云原生时代。他们希望使用较新的应用框架来构建微服务,以便它们在 Kubernetes 中非常高效地运行。Quarkus 是为了满足这一需求,它是真正针对微服务、无服务器、事件驱动的应用框架。

Quarkus 的架构

Quarkus 被称为"超音速亚原子 Java"。Quarkus 优化了 Java 框架,使其更具模块化、减少了框架本身的依赖性。Quarkus 基于 GraalVM,也支持 JVM。GraalVM 是一套通用型虚拟机,能执行各类高性能与互操作性任务,并在无需额外成本的前提下允许用户构建多语言应用程序,如下图 3 所示:

图 3. GraalVM 架构

67rUZzR.png!web

67rUZzR.png!web

在传统的 JVM 中运行应用启动速度会比较慢。GraalVM 可以为现有基于 JVM 的应用创建 Native Image 的功能(即本机可执行二进制文件)。生成的本机二进制文件以机器代码形式包含整个程序,可以直接运行。

正是由于 Quarkus 本身针对传统 Java 进行了优化,同时它可以运行在 GraalVM 上,因此它的启动速度很快、运行时消耗的内存很小。针对 Quarkus 的特点,总结如下:

  • 容器优先:最小的 Java 应用程序,最适合在容器中运行。
  • Cloud Native:符合微服务 12 要素架构。
  • 统一命令式和响应式:在一种编程模型下实现非阻塞式和命令式开发风格。
  • 基于标准:支持多种的标准和框架(RESTEasy,Hibernate,Netty,Eclipse Vert.x,Apache Camel)。
  • 微服务优先:缩短了启动时间,使 Java 应用程序可以执行代码转换。

接下来,我们通过实验的方式,验证基于 Quarkus 的特性。

验证 Quarkus 功能

我们采用如下实验环境来验证 Quarkus:

  • RHEL 7.6
  • Quarkus 0.21.2
  • OpenShift 3.11
  • Graal VM 19.1.1

接下来,我们通过实验环境分别验证:

  • 编译和部署 Quarkus 应用
  • Quarkus 的热加载
  • 在 OpenShift 中部署 Quarkus 应用程序
  • Quarkus 添加 Rest Client 扩展
  • Quarkus 容错能力

编译和部署 Quarkus 应用

实验环境是由两个节点(RHEL7.6)组成的 OpenShift 集群,如清单 1 所示:

清单 1. 查看 OpenShift 集群

[root@master ~]# oc get nodes
NAME                 STATUS    ROLES          AGE       VERSION
master.example.com   Ready     infra,master   339d      v1.11.0+d4cacc0
node.example.com     Ready     compute        339d      v1.11.0+d4cacc0

从 github 上下载 Quarkus 测试代码,如清单 2 所示:

清单 2. 下载 Quarkus 测试代码

[root@master ~]# git clone https://github.com/redhat-developer-demos/quarkus-tutorial
Cloning into 'quarkus-tutorial'...
remote: Enumerating objects: 86, done.
remote: Counting objects: 100% (86/86), done.
remote: Compressing objects: 100% (60/60), done.
Receiving objects: 100% (888/888), 1.36 MiB | 73.00 KiB/s, done.
remote: Total 888 (delta 44), reused 56 (delta 21), pack-reused 802
Resolving deltas: 100% (439/439), done.

在 OpenShift 中创建项目 quarkustutorial,用于后续部署容器化应用。

[root@master ~]# oc new-project quarkustutorial

设置环境变量,如清单 3 所示:

清单 3. 设置环境变量

[root@master ~]# cd quarkus-tutorial
[root@master quarkus-tutorial]# export TUTORIAL_HOME=`pwd`
[root@master quarkus-tutorial]# export QUARKUS_VERSION=0.21.2

在 RHEL 中创建 Quarkus 项目,如清单 4 所示:

清单 4. 创建 Quarkus 项目

mvn io.quarkus:quarkus-maven-plugin:$QUARKUS_VERSION:create \
  -DprojectGroupId="com.example" \
  -DprojectArtifactId="fruits-app" \
  -DprojectVersion="1.0-SNAPSHOT" \
  -DclassName="FruitResource" \
  -Dpath="fruit"

创建成功结果如图 4 所示:

图 4. 成功创建 Quarkus 项目

vQfANbV.png!web

vQfANbV.png!web

查看项目中生成的文件,如清单 5 所示:

清单 5. Quarkus 项目内容

[root@master quarkus-tutorial]# ls -al /root/quarkus-tutorial/work/fruits-app/.
total 32
drwxr-xr-x. 4 root root   111 Sep 24 18:12 .
drwxr-xr-x. 3 root root    41 Sep 24 18:08 ..
-rw-r--r--. 1 root root    53 Sep 24 18:11 .dockerignore
-rw-r--r--. 1 root root   295 Sep 24 18:11 .gitignore
drwxr-xr-x. 3 root root    21 Sep 24 18:12 .mvn
-rwxrwxr-x. 1 root root 10078 Sep 24 18:12 mvnw
-rw-rw-r--. 1 root root  6609 Sep 24 18:12 mvnw.cmd
-rw-r--r--. 1 root root  3693 Sep 24 18:11 pom.xml
drwxr-xr-x. 4 root root    30 Sep 24 18:11 src

我们查看应用的源码,如清单 6 所示:

#cat src/main/java/com/example/FruitResource.java

清单 6. 查看应用源代码

package com.example;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/fruit")
public class FruitResource {

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

上面代码定义了定了一个名为 /fruit 的 URI,通过 get 访问时返回 "hello"。

接下来,我们分别通过 JVM 和 Native 方式生成并运行 Quarkus 应用程序。

首先通过传统的 JVM 模式生成应用,编译成功结果如图 5 所示:

./mvnw -DskipTests clean package

图 5. 源码编译成功

Nz2eEzj.png!web

Nz2eEzj.png!web

查看编译生成的 jar 文件,如清单 7 所示:

清单 7. 查看生成的应用

[root@node fruits-app]# ls -al target/fruits-app-1.0-SNAPSHOT-runner.jar
-rw-r--r--. 1 root root 114363 Sep 24 18:19 target/fruits-app-1.0-SNAPSHOT-runner.jar

接下来,以 JVM 的方式运行应用,如清单 8 所示:

清单 8. 运行应用

[root@node fruits-app]# java -jar target/fruits-app-1.0-SNAPSHOT-runner.jar
2019-09-24 18:20:29,785 INFO  [io.quarkus] (main) Quarkus 0.21.2 started in 1.193s. Listening on: http://[::]:8080
2019-09-24 18:20:29,837 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy]

应用运行以后, 通过浏览器访问应用,可以看到返回值是 hello,如图 6 所示:

图 6. 浏览器访问应用

E3URR3n.png!web

E3URR3n.png!web

接下来,我们验证 Docker-Native 的模式来编辑应用,生成二进制文件。在编译的过程会使用红帽提供的 docker image,构建成功后在 target 目录中生成独立的二进制文件。执行如下命令启动编译:

[root@node fruits-app]# ./mvnw package -DskipTests -Pnative -Dquarkus.native.container-build=true

编译过程如图 7 所示,Quarkus 的 Docker-Native 编译过程会先生成 jar 文件 fruits-app-1.0-SNAPSHOT-runner.jar(这个 jar 文件和基于 JVM 方式编译成功的 jar 文件有所区别)。然后调用红帽的容器镜像 ubi-quarkus-native-image,从 jar 文件生成二进制可执行文件 fruits-app-1.0-SNAPSHOT-runner,如图 7 所示:

图 7. Quarkus Docker-Native 编译过程

vEZZneu.png!web

vEZZneu.png!web

JzUvuqA.png!web

JzUvuqA.png!web

从 fruits-app-1.0-SNAPSHOT-runner.jar 文件到二进制构建过程中会嵌入一些库文件(这些库文件是生成 fruits-app-1.0-SNAPSHOT-runner.jar 文件时产生的),以 class 的形式存到二进制文件中。lib 目录中包含二进制文件 fruits-app-1.0-SNAPSHOT-runner 运行所需要内容,如 org.graalvm.sdk.graal-sdk-19.2.0.1.jar,如清单 9 所示。

清单 9. 查看为二进制文件构建的库文件

[root@node target]# cd fruits-app-1.0-SNAPSHOT-native-image-source-jar
[root@node fruits-app-1.0-SNAPSHOT-native-image-source-jar]# ls
fruits-app-1.0-SNAPSHOT-runner  fruits-app-1.0-SNAPSHOT-runner.jar  lib
[root@node fruits-app-1.0-SNAPSHOT-native-image-source-jar]# ls lib/* |grep -i gra
lib/org.graalvm.sdk.graal-sdk-19.2.0.1.jar

查看生成的二进制文件 fruits-app-1.0-SNAPSHOT-runner,直接在 RHEL7 中运行,如清单 9 所示:

[root@node fruits-app]# ls -al target/fruits-app-1.0-SNAPSHOT-runner
-rwxr-xr-x. 1 root root 23092264 Nov  7 22:28 target/fruits-app-1.0-SNAPSHOT-runner
[root@node target]# ./fruits-app-1.0-SNAPSHOT-runner
2019-11-08 06:37:13,852 INFO  [io.quarkus] (main) fruits-app 1.0-SNAPSHOT (running on Quarkus 0.27.0) started in 0.012s. Listening on: http://0.0.0.0:8080
2019-11-08 06:37:13,852 INFO  [io.quarkus] (main) Profile prod activated.
2019-11-08 06:37:13,852 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy]

通过浏览器访问应用,结果正常,如图 8 所示:

图 8. 应用访问结果

uMNfq2j.png!web

uMNfq2j.png!web

从上面内容我们可以了解到:Quarkus Native 的构建环境需要完整的 GraalVM 环境(RHEL 中安装或以容器方式运行),而编译成功的二进制文件,已经包含 GraalVM 的运行时,可以直接在操作系统或容器中直接运行。

生成的二进制文件也可以用容器的方式运行,即构建 Docker Image。构建有两种方式:基于传统的 JVM 或基于 Native 的方式。

传统 JVM 模式运行的 docker file 如清单 9 所示,我们可以看到 docker file 使用的基础镜像是 openjdk8。

清单 9. JVM 模式运行的 Docker file

[root@node docker]# cat Dockerfile.jvm
FROM fabric8/java-alpine-openjdk8-jre
ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV AB_ENABLED=jmx_exporter
COPY target/lib/* /deployments/lib/
COPY target/*-runner.jar /deployments/app.jar
EXPOSE 8080

# run with user 1001 and be prepared for be running in OpenShift too
RUN adduser -G root --no-create-home --disabled-password 1001 \
  && chown -R 1001 /deployments \
  && chmod -R "g+rwX" /deployments \
  && chown -R 1001:root /deployments
USER 1001

ENTRYPOINT [ "/deployments/run-java.sh" ]

Native 模式运行的 docker file 如清单 10 所示,使用的基础镜像是 ubi-minimal。UBI 的全称是:Universal Base Image,这是红帽 RHEL 最轻量级的基础容器镜像。

清单 10. Native 模式运行的 Docker file

[root@node docker]# cat Dockerfile.native
FROM registry.access.redhat.com/ubi8/ubi-minimal
WORKDIR /work/
COPY target/*-runner /work/application
RUN chmod 775 /work
EXPOSE 8080
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]

在构建的时候,推荐使用 Dockerfile.native 模式构建 docker image,构建并运行的命令如下:

[root@node fruits-app]# docker build -f src/main/docker/Dockerfile.native -t example/fruits-app:1.0-SNAPSHOT . && \
> docker run -it --rm -p 8080:8080 example/fruits-app:1.0-SNAPSHOT

命令执行结果如下图 9 所示:

图 9. Native 模式构建应用的 docker image

RRf6jmN.png!web

RRf6jmN.png!web

查看容器运行情况,可以正常运行,docker image 的名称是 fruits-app:1.0-SNAPSHOT。

[root@node ~]# docker ps
CONTAINER ID        IMAGE                             COMMAND                  CREATED             STATUS              PORTS                    NAMES
ae46922cd0cf        example/fruits-app:1.0-SNAPSHOT   "./application -Dq..."   57 seconds ago      Up 57 seconds       0.0.0.0:8080->8080/tcp   nervous_bartik

至此,我们完成了 Quarkus 应用构建和运行的验证。

Quarkus 的热加载

接下来,我们验证 Quarkus 应用在开发模式的热加载功能。以开发模式启动应用后,修改应用源代码无需重新编译和重新运行,应用而直接生效。如果是 web 应用,在前台刷新浏览器即可看到更新结果。Quarkus 的开发模式非常适合应用调试阶段、经常需要调整源码并验证效果的需求。

以开发模式编译并热部署应用,如清单 11 所示:

清单 11. 通过开发模式启动应用

[root@master fruits-app]# ./mvnw compile quarkus:dev
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< com.example:fruits-app >-----------------------
[INFO] Building fruits-app 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ fruits-app ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ fruits-app ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- quarkus-maven-plugin:0.21.2:dev (default-cli) @ fruits-app ---
Listening for transport dt_socket at address: 5005
2019-09-24 21:18:06,422 INFO  [io.qua.dep.QuarkusAugmentor] (main) Beginning quarkus augmentation
2019-09-24 21:18:07,572 INFO  [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 1150ms
2019-09-24 21:18:07,918 INFO  [io.quarkus] (main) Quarkus 0.21.2 started in 1.954s. Listening on: http://[::]:8080
2019-09-24 21:18:07,921 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy]

应用启动成功后,通过浏览器访问效果如图 10 所示:

图 10. 应用访问结果

uMNfq2j.png!web

uMNfq2j.png!web

接下来,修改源码文件 src/main/java/com/example/FruitResource.java,将访问返回从 "hello" 修改为 "hello Davidwei!",如清单 12 所示:

清单 12. 修改应用源代码

package com.example;

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

@Path("/fruit")
public class FruitResource {

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

直接刷新浏览器,如图 11 所示,我们看到浏览的返回与此前在源码中修改的内容一致。

图 11. 应用访问结果

AN3miiE.png!web

AN3miiE.png!web

至此,我们验证完了 Quarkus 应用的热加载功能。

在 OpenShift 中部署 Quarkus 应用程序

要将 Quarkus 应用部署到 OpenShift 中,首先需要添加 Quarkus Kubernetes 扩展(清单 13 中包含)。

Quarkus 扩展

Quarkers 的扩展是一组依赖项,可以将它们添加到 Quarkus 项目中,从而获得特定的功能,例如健康检查等。扩展将配置或引导框架或技术集成到 Quarkus 应用程序中。通过命令行可以列出 Quarkers 可用和支持的扩展,如清单 13 所示:

清单 13. 查看 Quarkus 扩展

[root@master fruits-app]# ./mvnw quarkus:list-extensions
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< com.example:fruits-app >-----------------------
[INFO] Building fruits-app 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- quarkus-maven-plugin:0.21.2:list-extensions (default-cli) @ fruits-app ---

Current Quarkus extensions available:
Agroal - Database connection pool                  quarkus-agroal
Amazon DynamoDB                                    quarkus-amazon-dynamodb
Apache Kafka Client                                quarkus-kafka-client
Apache Kafka Streams                               quarkus-kafka-streams
Apache Tika                                        quarkus-tika
Arc                                                quarkus-arc
AWS Lambda                                         quarkus-amazon-lambda
Flyway                                             quarkus-flyway
Hibernate ORM                                      quarkus-hibernate-orm
Hibernate ORM with Panache                         quarkus-hibernate-orm-panache
Hibernate Search + Elasticsearch                   quarkus-hibernate-search-elasticsearch
Hibernate Validator                                quarkus-hibernate-validator
Infinispan Client                                  quarkus-infinispan-client
JDBC Driver - H2                                   quarkus-jdbc-h2
JDBC Driver - MariaDB                              quarkus-jdbc-mariadb
JDBC Driver - PostgreSQL                           quarkus-jdbc-postgresql
Jackson                                            quarkus-jackson
JSON-B                                             quarkus-jsonb
JSON-P                                             quarkus-jsonp
Keycloak                                           quarkus-keycloak
Kogito                                             quarkus-kogito
Kotlin                                             quarkus-kotlin
Kubernetes                                         quarkus-kubernetes
Kubernetes Client                                  quarkus-kubernetes-client
Mailer                                             quarkus-mailer
MongoDB Client                                     quarkus-mongodb-client
Narayana JTA - Transaction manager                 quarkus-narayana-jta
Neo4j client                                       quarkus-neo4j
Reactive PostgreSQL Client                         quarkus-reactive-pg-client
RESTEasy                                           quarkus-resteasy
RESTEasy - JSON-B                                  quarkus-resteasy-jsonb
RESTEasy - Jackson                                 quarkus-resteasy-jackson
Scheduler                                          quarkus-scheduler
Security                                           quarkus-elytron-security
Security OAuth2                                    quarkus-elytron-security-oauth2
SmallRye Context Propagation                       quarkus-smallrye-context-propagation
SmallRye Fault Tolerance                           quarkus-smallrye-fault-tolerance
SmallRye Health                                    quarkus-smallrye-health
SmallRye JWT                                       quarkus-smallrye-jwt
SmallRye Metrics                                   quarkus-smallrye-metrics
SmallRye OpenAPI                                   quarkus-smallrye-openapi
SmallRye OpenTracing                               quarkus-smallrye-opentracing
SmallRye Reactive Streams Operators                quarkus-smallrye-reactive-streams-operators
SmallRye Reactive Type Converters                  quarkus-smallrye-reactive-type-converters
SmallRye Reactive Messaging                        quarkus-smallrye-reactive-messaging
SmallRye Reactive Messaging - Kafka Connector      quarkus-smallrye-reactive-messaging-kafka
SmallRye Reactive Messaging - AMQP Connector       quarkus-smallrye-reactive-messaging-amqp
REST Client                                        quarkus-rest-client
Spring DI compatibility layer                      quarkus-spring-di
Spring Web compatibility layer                     quarkus-spring-web
Swagger UI                                         quarkus-swagger-ui
Undertow                                           quarkus-undertow
Undertow WebSockets                                quarkus-undertow-websockets
Eclipse Vert.x                                     quarkus-vertx

添加 Quarkus Kubernetes 扩展,该扩展使用 Dekorate 生成默认的 Kubernetes 资源模板,如清单 14 所示:

清单 14. 添加 Quarkus Kubernetes 扩展

[root@master fruits-app]# ./mvnw quarkus:add-extension -Dextensions="quarkus-kubernetes"
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< com.example:fruits-app >-----------------------
[INFO] Building fruits-app 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- quarkus-maven-plugin:0.21.2:add-extension (default-cli) @ fruits-app ---
  Adding extension io.quarkus:quarkus-kubernetes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.466 s
[INFO] Finished at: 2019-09-24T23:27:21-07:00
[INFO] ------------------------------------------------------------------------

配置用于部署到 OpenShift 的容器和组和名称,将以下属性加到 src/main/resources /application.properties,如清单 15 所示:

清单 15. 添加应用参数

[root@master resources]# cat application.properties
quarkus.kubernetes.group=example
quarkus.application.name=fruits-app

接下来,运行 Maven 目标来生成 Kubernetes 资源,命令执行结果如图 12 所示:

./mvnw package -DskipTests

图 12. 生成 Kubernetes 资源

rMjuY3b.png!web

rMjuY3b.png!web

接下来,我们检查自动生成的 Kubernetes 资源,如清单 16 所示(这里使用上面步骤中生成的容器镜像 fruits-app:1.0-SNAPSHOT):

清单 16. 查看生成的 Kubernetes 资源

[root@master fruits-app]# cat target/wiring-classes/META-INF/kubernetes/kubernetes.yml
---
apiVersion: "v1"
kind: "List"
items:
- apiVersion: "v1"
  kind: "Service"
  metadata:
    labels:
      app: "fruits-app"
      version: "1.0-SNAPSHOT"
      group: "example"
    name: "fruits-app"
  spec:
    ports:
    - name: "http"
      port: 8080
      targetPort: 8080
    selector:
      app: "fruits-app"
      version: "1.0-SNAPSHOT"
      group: "example"
    type: "ClusterIP"
- apiVersion: "apps/v1"
  kind: "Deployment"
  metadata:
    labels:
      app: "fruits-app"
      version: "1.0-SNAPSHOT"
      group: "example"
    name: "fruits-app"
  spec:
    replicas: 1
    selector:
      matchLabels:
        app: "fruits-app"
        version: "1.0-SNAPSHOT"
        group: "example"
    template:
      metadata:
        labels:
          app: "fruits-app"
          version: "1.0-SNAPSHOT"
          group: "example"
      spec:
        containers:
        - env:
          - name: "KUBERNETES_NAMESPACE"
            valueFrom:
              fieldRef:
                fieldPath: "metadata.namespace"
          image: "example/fruits-app:1.0-SNAPSHOT"
          imagePullPolicy: "IfNotPresent"
          name: "fruits-app"
          ports:
          - containerPort: 8080
            name: "http"
            protocol: "TCP"

在 OpenShift 中应用 Kubernetes 资源:

[root@master fruits-app]# oc apply -f  target/wiring-classes/META-INF/kubernetes/kubernetes.yml
service/fruits-app created
deployment.apps/fruits-app created

执行上述命令后,包含应用的 pod 会被自动创建,如图 13 所示:

图 13. 查看生成的 pod

ZN7BnqN.png!web

ZN7BnqN.png!web

在 OpenShift 中创建路由。

[root@master ~]# oc expose service fruits-app
route.route.openshift.io/fruits-app exposed

通过 curl 验证调用应用 fruit URI 的返回值,确保应用运行正常:

[root@master ~]# SVC_URL=$(oc get routes fruits-app -o jsonpath='{.spec.host}')

[root@master ~]# curl $SVC_URL/fruit
Hello DavidWei!

至此,我们成功将 Quarkus 应用部署到了 OpenShift 上。

Quarkus 应用添加 Rest Client 扩展

在微服务架构中,应用如果要访问外部 RESTful Web 服务,Quarkus 需要按照 MicroProfile Rest Client 规范提供 Rest 客户端。

针对 fruits-app,我们创建一个可以访问 http://www.fruityvice.com 的 Rest 客户端,以获取有关水果的营养成分。我们查看 RESTful Web 服务的页面,通过 get 方式可以查看所有水果的相信信息,如图 14 所示:

图 14. 通过 RESTful Web 应用查看所有水果信息

UbMJ3mI.png!web

UbMJ3mI.png!web

查看香蕉的营养成分,如图 15 所示:

图 15. 通过 RESTful Web 应用查看香蕉的营养成分

Yn2AR3M.png!web

Yn2AR3M.png!web

为了让 fruits-app 应用能够访问 RESTful Web 应用,我们将其添加 Rest Client 和 JSON-B 扩展(quarkus-rest-client、quarkus-resteasy-jsonb)。运行以下命令进行添加,执行结果如图 16 所示:

./mvnw quarkus:add-extension -Dextension="quarkus-rest-client, quarkus-resteasy-jsonb"

图 16. 为 Quarkus 应用添加 Rest Client 和 JSON-B 扩展

7FZbmma.png!web

7FZbmma.png!web

我们还需要创建一个 POJO 对象,该对象用于将 JSON 消息从 http://www.fruityvice.com 反序列化为 Java 对象。

在 src/main/java/com/example 中创建

为 FruityVice 的新 Java 文件,其内容如清单 17 所示:

清单 17. 创建 FruityVice Java 文件

[root@master example]# cat FruityVice
package com.example;

public class FruityVice {

    public static FruityVice EMPTY_FRUIT = new FruityVice();

    private String name;
    private Nutritions nutritions;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Nutritions getNutritions() {
        return nutritions;
    }

    public void setNutritions(Nutritions nutritions) {
        this.nutritions = nutritions;
    }

    public static class Nutritions {
        private double fat;
        private int calories;

        public double getFat() {
            return fat;
        }

        public void setFat(double fat) {
            this.fat = fat;
        }

        public int getCalories() {
            return calories;
        }

        public void setCalories(int calories) {
            this.calories = calories;
        }

    }
}

接下来创建一个 Java 接口,该接口充当代码和外部服务之间的客户端。在 src/main/java/com/example 中创建

为 FruityViceService 的新 Java 文件,内容如清单 18 所示:

清单 18. 创建 FruityViceService Java 文件

[root@master example]# cat FruityViceService
package com.example;

import java.util.List;

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

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@Path("/api")
@RegisterRestClient
public interface FruityViceService {

    @GET
    @Path("/fruit/all")
    @Produces(MediaType.APPLICATION_JSON)
    public List<FruityVice> getAllFruits();

    @GET
    @Path("/fruit/{name}")
    @Produces(MediaType.APPLICATION_JSON)
    public FruityVice getFruitByName(@PathParam("name") String name);

}

配置 FruityVice 服务,将以下属性添加到 src/main/resources/application.properties 文件中,如清单 19 所示:

清单 19. 修改应用参数文件

[root@master fruits-app]# cat src/main/resources/application.properties
quarkus.kubernetes.group=example
quarkus.application.name=fruits-app
com.example.FruityViceService/mp-rest/url=http://www.fruityvice.com

最后,修改 src/main/java/com/example/FruitResource.java,增加 FruityViceService 的调用,如清单 20 所示:

清单 20. 修改应用源码文件

[root@master fruits-app]# cat src/main/java/com/example/FruitResource.java
package com.example;

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

import org.eclipse.microprofile.rest.client.inject.RestClient;

import com.example.FruityViceService;

@Path("/fruit")
public class FruitResource {

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


@RestClient
FruityViceService fruityViceService;
@Path("{name}")
@GET
@Produces(MediaType.APPLICATION_JSON)
public FruityVice getFruitInfoByName(@PathParam("name") String name) {
    return fruityViceService.getFruitByName(name);
}
}

我们以开发模式启动应用程序,命令执行结果如图 17 所示:

./mvnw compile quarkus:dev

图 17. 以开发模式启动应用

RrIJJvm.png!web

RrIJJvm.png!web

我们通过浏览器访问应用,查看香蕉的营养成分,成功返回信息,如图 18 所示:

图 18. 访问应用查看香蕉的影响成分

rMfMVbZ.png!web

rMfMVbZ.png!web

至此,我们成功验证了为 Quarkus 应用添加 Rest Client 扩展。

Quarkus 应用的容错能力

在微服务中,容错是非常重要的。在以往的方法中,我可以通过微服务治理框架来实现(如 Spring Cloud);在 Quarkus 应用中,Quarkus 与 MicroProfile Fault Tolerance 规范集成提供原生的容错功能。

我们为 Quarkus 应用程序添加 Fault Tolerance 扩展(quarkus-smallrye-fault-tolerance),执行如下命令,执行结果如图 19 所示:

./mvnw quarkus:add-extension -Dextension="quarkus-smallrye-fault-tolerance"

图 19. 为 Quarkus 添加 Fault Tolernace 扩展

yaEnAv7.png!web

yaEnAv7.png!web

接下来在 FruityViceService 中添加重试策略。添加 org.eclipse.microprofile.faulttolerance.Retry 到源码文件 src/main/java/java/com/example/FruityViceService.java 中,并添加错误重试的次数和时间(maxRetries = 3, delay = 2000)如清单 21 所示:

清单 21. 修改应用源码文件

package com.example;
import java.util.List;

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

import org.eclipse.microprofile.faulttolerance.Retry;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@Path("/api")
@RegisterRestClient
public interface FruityViceService {

    @GET
    @Path("/fruit/all")
    @Produces(MediaType.APPLICATION_JSON)
    public List<FruityVice> getAllFruits();

    @GET
    @Path("/fruit/{name}")
    @Produces(MediaType.APPLICATION_JSON)
    @Retry(maxRetries = 3, delay = 2000)
    public FruityVice getFruitByName(@PathParam("name") String name);

}

完成配置后,如果访问应用出现任何错误,将自动执行 3 次重试,两次重试之间等待 2 秒钟。

接下来,我们以开发模式编译并加载应用。

./mvnw compile quarkus:dev

应用启动后,将实验环境访问外部互联网的连接断掉,并再次对应用发起请求: http://localhost:8080/fruit/banana 。在等待大约 6 秒后(3 次重试,每次等待 2 秒)后,将会出发异常报错,这符合我们的预期,如清单 22 所示:

清单 22. 访问应用异常报错

Caused by: javax.ws.rs.ProcessingException: RESTEASY004655: Unable to invoke request: java.net.UnknownHostException: www.fruityvice.com
Caused by: java.net.UnknownHostException: www.fruityvice.com

有时候,我们并不需要在应用前台报错时显示代码内部内容。出于这个目的,我们修改源 FruityViceService,添加 org.eclipse.microprofile.faulttolerance.Fallback,使用 MicroProfile 的 Fallback 框架,这样当应用无法访问时,返回空(return FruityVice.EMPTY_FRUIT;),如清单 23 所示:

清单 23. 访问应用异常报错

package com.example;

import java.util.List;

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

import org.eclipse.microprofile.faulttolerance.ExecutionContext;
import org.eclipse.microprofile.faulttolerance.Fallback;
import org.eclipse.microprofile.faulttolerance.FallbackHandler;
import org.eclipse.microprofile.faulttolerance.Retry;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@Path("/api")
@RegisterRestClient
public interface FruityViceService {

    @GET
    @Path("/fruit/all")
    @Produces(MediaType.APPLICATION_JSON)
    public List<FruityVice> getAllFruits();



    @GET
    @Path("/fruit/{name}")
    @Produces(MediaType.APPLICATION_JSON)
    @Retry(maxRetries = 3, delay = 2000)
    @Fallback(value = FruityViceRecovery.class)
    public FruityVice getFruitByName(@PathParam("name") String name);

    public static class FruityViceRecovery implements FallbackHandler<FruityVice> {

    @Override
    public FruityVice handle(ExecutionContext context) {
        return FruityVice.EMPTY_FRUIT;
}

}



}

我们断开对外部互联网的访问,再次访问应用,当超时以后,返回空值,如图 20 所示:

图 20. 应用访问返回空值

Bvqqu26.png!web

Bvqqu26.png!web

截止到目前,我们验证完了 Quarkus 应用的容错能力。

结束语

通过本文,相信您对 Quarkus 架构有了一定的理解。随着云原生的理念不断普及,相信越来越多的 Java 开发者会关注 Quarkus 架构。而 Quarkus 的轻量级和性能高的优势,也势必会在未来云原生应用中大放异彩!

参考资源


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK