3.4 验证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应用的容错能力

3.4.1 编译和部署Quarkus应用

实验环境是由两个节点(RHEL 7.6)组成的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测试代码,如下所示:


[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

设置环境变量,如下所示:


[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项目,如下所示:


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

创建成功结果如图3-14所示。

图3-14 成功创建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

我们查看应用的源码,如下所示:


#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.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模式生成应用,编译成功结果如图3-15所示。


./mvnw -DskipTests clean package

图3-15 源码编译成功

查看编译生成的jar文件,如下所示:


[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的方式运行应用,如下所示:


[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,如图3-16所示。

图3-16 浏览器访问应用

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


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

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

图3-17 Quarkus Docker-Native编译过程

从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,如下所示:


[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,直接在RHEL 7中运行,如下所示:


[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]

通过浏览器访问应用,结果正常,如图3-18所示。

图3-18 应用访问结果

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

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

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


[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如下所示,使用的基础镜像是ubi-minimal。UBI的全称是Universal Base Image,这是Red HatRHEL最轻量级的基础容器镜像,如下所示:


[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

命令执行结果如图3-19所示。

图3-19 Native模式构建应用的docker image

查看容器运行情况,可以正常运行,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应用构建和运行的验证。

3.4.2 Quarkus的热加载

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

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


[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 augmen-tation 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]

应用启动成功后,通过浏览器访问效果如图3-20所示。

图3-20 应用访问结果

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


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!";
    }
}

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

图3-21 应用访问结果

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

3.4.3 在OpenShift中部署Quarkus应用程序

要将Quarkus应用部署到OpenShift中,首先需要添加Quarkus Kubernetes扩展。

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


[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资源模板,如下所示:


[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,如下所示:


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

接下来,运行Maven目标来生成Kubernetes资源,命令执行结果如图3-22所示。


./mvnw package -DskipTests

图3-22 生成Kubernetes资源

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


[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会被自动创建,如图3-23所示。

图3-23 查看生成的Pod

在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上。

3.4.4 Quarkus应用添加REST Client扩展

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

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

图3-24 通过RESTful Web应用查看所有水果信息

查看香蕉的营养成分,如图3-25所示。

图3-25 通过RESTful Web应用查看香蕉的营养成分

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


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

图3-26 为Quarkus应用添加REST Client和JSON-B扩展

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

在src/main/java/com/example中创建名为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文件,内容如下所示:


[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文件中,如下所示:


[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的调用,如下所示:


[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);
}
}

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


./mvnw compile quarkus:dev

图3-27 以开发模式启动应用

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

图3-28 访问应用查看香蕉的营养成分

至此,我们成功完成了对Quarkus应用添加REST Client扩展的验证。

3.4.5 Quarkus应用的容错能力

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

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


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

图3-29 为Quarkus添加Fault Tolernace扩展

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


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秒)后,将会发出异常报错,这符合我们的预期,如下所示:


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,使用Micro-Profile的Fallback框架,这样当应用无法访问时,会返回空(return FruityVice.EMPTY_FRUIT;),如下所示:


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;
}

}
}

我们断开对外部互联网的访问,再次访问应用,当超时后会返回空值,如图3-30所示。

图3-30 应用访问返回空值

至此,我们成功完成了对Quarkus应用的容错能力的验证。