使用Quarkus在Hibernate-Panache中创建REST API,并通过Jaeger进行跟踪进行本地化(2019年11月版)

我想用 Quarkus 创建一个 REST API 并通过 Jaeger 进行查看。

(Wǒ Quarkus REST API Jaeger .)

我之前在一篇文章中介绍了如何在 Apollo Server 上使用 Jaeger 进行追踪日志检查来实现 Opentracing,但由于 Quarkus 是支持 MicroProfile 的,因此 Opentracing 的集成也非常简单!因此,今天我想挑战在 Quarkus 中创建和本地化 REST API,并使用 Jaeger 来显示追踪日志。

這裡提供參考頁面等相關資訊

由于Quarkus的官方手册网站内容相当庞大,因此本次只介绍本步骤所参考的页面。

我們將以以下頁面為基礎來進行 REST API 的開發。

    QUARKUS – WRITING JSON REST SERVICES

请参考以下页面了解有关持续化的信息,尝试使用Hibernate-Panache。

    Quarkus – Simplified Hibernate ORM with Panache

在以下的官方存储库中,有一个使用Hibernate-Panache构建REST API的示例。

    hibernate-orm-panache-resteasy | github

接下来,我们将参考以下页面来了解与Jaeger协作的Opentracing支持。

    QUARKUS – USING OPENTRACING

有关Swagger支持的信息可在以下页面找到。

    QUARKUS – USING OPENAPI AND SWAGGER UI

最后,关于本地构建的相关信息,请参考以下页面。

    QUARKUS – BUILDING A NATIVE EXECUTABLE

适用环境

操作系统:macOS Mojave 10.14.6
Docker:Docker Desktop 2.1.0.4

为了从容器内访问宿主机,我在 Docker Desktop 中采取了一种简便的方法。抱歉。。。
另外,如果您在 Windows 或 Mac 上使用 Docker Desktop,请分配至少 6G 内存。
构建本地化的编译需要使用超出预期的内存。

请准备一个在本地使用的 JDK 8或更高版本,并且准备 Maven。

那么,我来了!

1. 创建Quarkus项目

首先,我们使用以下Maven命令创建一个新的项目。

$ mvn io.quarkus:quarkus-maven-plugin:0.27.0:create
...
$ cd my-quarkus-project

请选择生成REST API和HelloResource的选项,然后随便回答问题就可以了。

$ mvn quarkus:add-extension -Dextensions="quarkus-jdbc-postgresql,quarkus-smallrye-metrics,quarkus-smallrye-openapi,quarkus-smallrye-opentracing,quarkus-hibernate-orm-panache,quarkus-resteasy-jsonb"

我要添加一个插件。这次的内容非常丰富。

使用Hibernate-Panache来创建服务和API。

好的,现在我们开始真正的编码工作吧!

2-1. 准备进行休眠操作

我們現在追加插件,並且立刻進行以下動作…我們要在 pom.xml 的 中新增以下的相依模組。

...
    <dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-jsr310</artifactId>
    </dependency>
    <dependency>
      <groupId>io.opentracing.contrib</groupId>
      <artifactId>opentracing-jdbc</artifactId>
    </dependency>
...

这种情况只有 VSCode 是不够方便的,所以我会想要有像 Eclipse 这样的 IDE 支持。我觉得这是 Java 不好的地方。。。

接下来,我们将在配置文件中添加以下内容,以设置 hibernate。

quarkus.datasource.url = jdbc:tracing:postgresql://docker.for.mac.host.internal:5432/mydatabase
quarkus.datasource.driver=io.opentracing.contrib.jdbc.TracingDriver
quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQLDialect
quarkus.datasource.username = postgres
quarkus.datasource.password = postgres
# drop and create the database at startup (use `update` to only update the schema)
quarkus.hibernate-orm.database.generation = drop-and-create

稍后,我将在容器中运行Quarkus,为了查看父主机,我正在使用docker.for.mac.host.internal。如果您使用的是Windows或Linux,请适当进行修改。

还有,连接设置中有一些奇怪的设置…如果透露秘密,为了在Jaeger中输出SQL的跟踪日志,我们在JDBC上插入了跟踪用的驱动程序。通常,Hibernate会通过驱动程序自动识别Dialect,但由于插入了跟踪用的驱动程序,无法再判断是否为PostgreSQL,因此直接指定了Dialect。

如果没有追踪日志驱动程序,以下简单设置即可。

quarkus.datasource.url = jdbc:postgresql://docker.for.mac.host.internal:5432/mydatabase
quarkus.datasource.driver = org.postgresql.Driver
quarkus.datasource.username = postgres
quarkus.datasource.password = postgres

此外,quarkus.hibernate-orm.database.generation 是指定架构迁移方法的选项。为了本次尝试中更多地进行试错,我们选择了删除并创建的方式。

2-2. 创建模型类

好了,终于要开始实现了。首先是从模型类开始。

import javax.persistence.Entity;
import java.time.LocalDate;

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

import com.fasterxml.jackson.annotation.JsonFormat;

@Entity
public class Person extends PanacheEntity {
  public enum Status {
    Alive, DECEASED
  }

  public String name;
  @JsonFormat(pattern = "yyyy-MM-dd")
  public LocalDate birth;
  public Status status;
}

我们将定义一个模型类,以PanacheEntity作为父类。这个Panache也是一个库,类似于之前介绍的TypeORM,可以使用”Active Record模式”轻松访问数据库。

这次我奢侈地使用了 Enum 和 LocalDate 作为成员。
由于默认情况下无法对 LocalDate 进行 JSON 序列化和反序列化,我使用了 JSONFormat 注解来指定格式。

另外,特别是对于未指定任何内容的”枚举类型”的状态,似乎可以顺利地转换为 JSON…真是令人难以置信。

2-3. 实现REST API接口部分

接下来的编码是关于REST API服务的接口部分。

package org.acme.quarkus.sample;

import javax.transaction.Transactional;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
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.metrics.MetricUnits;
import org.eclipse.microprofile.metrics.annotation.Timed;
import org.eclipse.microprofile.metrics.annotation.Counted;

import org.acme.quarkus.sample.model.Person;

@Path("/person")
@Produces(MediaType.APPLICATION_JSON)
public class PersonResource {

    @POST
    @Transactional
    @Counted(name = "performed_create", description = "How many it have been called.")
    @Timed(name = "checksTimer_create", description = "A measure of how long it takes to perform creating person.", unit = MetricUnits.MILLISECONDS)
    public Person create(Person person) {
        person.persist();
        return person;
    }

    @GET
    @Path("/{id}")
    @Transactional
    @Counted(name = "performed_get", description = "How many it have been called.")
    @Timed(name = "checksTimer_get", description = "A measure of how long it takes to perform getting person.", unit = MetricUnits.MILLISECONDS)
    public Person get(@PathParam("id") Long id) {
        return Person.findById(id);
    }
}

暫時先建了一個只包含 “create” 和 “get” 的簡單 API 用於測試動作。

通过 “create” 的参数,直接接收 Person 对象。这意味着可以自动从 JSON 中进行 Person 的转换。
然后,只是将转换后的 JSON 存入数据库的 Person 对象 person 还未被写入,所以使用 person.persist() 方法将其保存到数据库。在此过程中,还会自动分配一个 ID 给 person 并返回。非常方便。

我正在使用get来调用Person类的类方法findById。连Lombok都要大吃一惊。

附带提到的@Timed、@Counted注解可能是不常见的,但它们定义了用于在Prometheus中记录指标的度量。

在方法内部的代码可以集中于纯粹的业务逻辑,通过注解可以进行URL路径和URL参数映射、Content-Type、事务、指标等定义。这就是JavaEE!

2-4. 添加Jaeger設定

现在,我们将进行与Jaeger的连接以及采样器的设置来收集跟踪日志。在这里,连接的目标主机仍然是docker.for.mac.host.internal。

quarkus.jaeger.endpoint=http://docker.for.mac.host.internal:14268/api/traces
quarkus.jaeger.service-name=sample.quarkus
quarkus.jaeger.sampler-type=const
quarkus.jaeger.sampler-param=1

服务名称将作为Jaeger的标识符。在这里我们使用 sample.quarkus。

quarkus.jaeger.sampler-type 和 quarkus.jaeger.sampler-param 随意设置。

2-5. Quarkus 服务器的端口设置

最终,我们通过以下配置指定了 Quarkus 在等待的端口。

quarkus.http.port=8082

Quarkus 的工作就到这里了!

3. 准备 Jager 和 PostgreSQL

只需要一个选项,用原生中文重新表述:使用docker-compose快速准备所需的Jaeger和PostgreSQL进行操作确认。

3-1. 创建一个docker-compose.yml文件。

创建一个类似以下的 docker-compose.yml 文件。

version : "3"
services:
  db:
    image: postgres
    ports:
      - 5432:5432
    environment:
      - POSTGRES_DB=mydatabase
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080
  jaeger:
    image: jaegertracing/all-in-one
    environment:
      - COLLECTOR_ZIPKIN_HTTP_PORT=9411
    ports:
      - 5775:5775/udp
      - 6831:6831/udp
      - 6832:6832/udp
      - 5778:5778
      - 16686:16686
      - 14268:14268
      - 9411:9411

请使用以下命令启动。

$ docker-compose up -d

先试用一下 Quarkus。

好吧,既然我们到这一步了,让我们先启动开发服务器试试看吧。请键入以下命令。

$ mvn compile qurkus:dev
...

让我们在浏览器中打开 http://localhost:8082。如果显示了 Quarkus 的起始页面,那就暂时没问题!

4. 本地化

一旦在开发服务器上确认运行正常后,我将尝试进行本地化转换。

根据创建一个具有多阶段Docker构建的容器的部分进行参考,我们将创建一个适用于本地编译的容器。

## Stage 1 : build with maven builder image with native capabilities
FROM quay.io/quarkus/centos-quarkus-maven:19.2.1 AS build
COPY src /usr/src/app/src
COPY pom.xml /usr/src/app
USER root
RUN chown -R quarkus /usr/src/app
USER quarkus
RUN mvn -f /usr/src/app/pom.xml -Pnative -Dmaven.test.skip=true clean package

## Stage 2 : create the docker final image
FROM registry.access.redhat.com/ubi8/ubi-minimal
WORKDIR /work/
COPY --from=build /usr/src/app/target/*-runner /work/application
RUN chmod 775 /work
EXPOSE 8080
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]

然后,使用以下命令进行构建!

$ docker build -t quarkus-rest-demo .
...

如果构建成功完成,quarkus-rest-demo镜像将包含原生化的二进制文件!

由于此容器单独使用大约4GB的内存,所以如果内存不足或其他容器正在运行,构建很可能会失败(事实上,如果内存非常紧张,构建会非常容易失败)。
即使只有这么一小段代码,根据环境的不同可能需要大约10分钟的时间。

5. 启动和运行确认

现在,让我们实际启动容器吧。如果你停止了PostgreSQL或Jaeger的容器,请先启动它们。

启动本地Quarkus服务。

请使用以下命令启动。

$ docker run -it quarkus-rest-demo

用终端进行POST请求。

我打开了另一个终端,并尝试使用以下命令将JSON数据通过PersonResource#create方法进行POST。

$ curl -H 'Content-Type:application/json' -d '{"name":"Alice","birth":"2010-10-11","status":"Alive"}' http://localhost:8082/person

{"id":1,"birth":"2010-10-11","name":"Alice","status":"Alive"}

当且如果ID被添加并且返回JSON,则表示成功!

5-3. 通过浏览器进行GET请求

让我们在浏览器中打开 http://localhost:8082/person/1。

在这里也能够获取到 {“id”:1,”birth”:”2010-10-11″,”name”:”Alice”,”status”:”Alive”} 吗?

5-4,使用Adminer进行确认。

让我们像往常一样使用Adminer进行确认。

打开浏览器并访问 http://locahost:8080,在Adminer中进行访问。使用上述设置的postgres/postgres/mydatabase登录,尝试在SQL命令中执行 select * from person; 然后…

SQLコマンド - db - Adminer.png

由于连打,已成功获取到多个记录!可以看出 ID 已经递增。
这些发号处理由 Panache 自动完成。

5-5. 用Jaeger确认一下

让我们终于来确认一下关于 Jaeger 的追踪日志的主题。

可以通过浏览器打开 http://localhost:16686,并打开Jaeger的控制台。
从服务名称中选择sample.quarkus,然后点击PersonResouce.create的日志来查看。

Jaeger UI native.png

这样一来,我们可以看到整个请求需要27毫秒,ID分配需要11毫秒,插入语句需要2毫秒。因此,由于还可以确认已发出的SQL语句,所以检查瓶颈变得更加容易了呢!

6. 让我们也来看看 Prometheus + cAdvisor + Grafana 是如何运作的

好吧,让我们再次仔细检查一下之前在文章中使用过的 cAdvisor。

6-1. dockprom的准备

这次我对设置进行了一些调整,所以从我的存储库中克隆,而不是从原始存储库中克隆。

$ git@github.com:Yoshinori-Koide-PRO/dockprom.git
...
$ cd dockprom

现在,我们需要在prometheus/prometheus.yml配置文件中调整Quarkus的主机地址。

...
  - job_name: 'quarkus'
    scrape_interval: 5s
    static_configs:
      - targets: ['docker.for.mac.host.internal:8082']
...

本次我們使用Docker Desktop,所以使用docker.for.mac.host.internal。但如果使用Linux,則需要建立一個名為.env的檔案,內容如下:

# ip -4 addr show docker0 | grep -Po 'inet \K[\d.]+' で取得できた IPを以下に記載
HOME_IP=172.17.0.1

将在.env文件中定义的HOME_IP传递给docker-compose.yml文件中容器的extra_host。

...
  prometheus:
    image: prom/prometheus:v2.13.1
    container_name: prometheus
    volumes:
      - ./prometheus/:/etc/prometheus/
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
      - '--storage.tsdb.retention.time=200h'
      - '--web.enable-lifecycle'
    restart: unless-stopped
    extra_hosts:
      - "docker_host:$HOME_IP"
    expose:
      - 9090
    networks:
      - monitor-net
    labels:
      org.label-schema.group: "monitoring"
...

通过这个设置,你可以通过主机名docker_host解析出父主机的IP地址。
由于Prometheus的配置文件无法使用环境变量等,所以这只能是一个权宜之计。

所以,使用 docker-compose up -d 命令就可以启动了!

6-2. 在Prometheus中的显示

首先是 Prometheus。

Prometheus Time Series Collection and Processing Server.png

随意选择,点击“图表”标签,将显示获取的数值的图表!

Prometheus Time Series Collection and Processing Server (1).png

在图形下方的图例?的字符串似乎是Prometheus查询。如果将其复制粘贴,这个图形就可以在Grafana中显示出来。

用cAdvisor + Grafana进行检查!

最后,让我们在Grafana中查看容器的状态。

Docker Containers - Grafana.png

在上面的截图中,显示为”recurring_blackwall”的是”quarkus-rest-demo”的容器。(容器名称未指定,所以……)
“test_env_db_1″是PostgreSQL的容器。

下面是内存使用量的图表。

Docker Containers - Grafana (1).png

虽然刚启动容器不久,但据看起来,它使用的内存比PostgreSQL要少得多…
简直难以置信是用Java编写的应用程序…
最终单位是10MB… 这个地方是最让人难以置信的地方…最近的Java应用服务器不是都使用GB单位来分配内存吗?!
整体的占用空间轻巧,让人感到安心啊~!

7. 确认 OpenAPI 的终端点。

好的,关于度量系统端点的检查已经完成了。接下来,让我们继续检查一下 OpenAPI 的端点吧!

7-1. Swagger UI:
Swagger界面

只有在开发服务器模式下,才能启动Swagger UI。请先使用 mvn quarkus:dev 启动,然后在浏览器中打开 http://localhost:8082/swagger-ui。

Swagger UI.png

熟悉的Swagger UI,可以显示公开API的描述文本和直接操作的控制台。如果要将Swagger UI本地化后继续公开,需要设置以下选项。

quarkus.swagger-ui.always-include=true

7-2. 开放应用程序接口

这是一个原生化之后仍然有效(当然有效)的OpenAPI端点。

在浏览器中打开 http://localhost:8082/openapi,将会下载以下类似的文本文件。

---
openapi: 3.0.1
info:
  title: Generated API
  version: "1.0"
paths:
  /hello:
    get:
      responses:
        200:
          description: OK
          content:
            text/plain:
              schema:
                type: string
  /person:
    post:
      responses:
        200:
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Person'
  /person/{id}:
    get:
      parameters:
...

是的,OpenAPI非常出色!

总而言之 / 总结

通过对Quarkus进行一些配置和注解的添加,可以确认与非常多的终端点进行协作。虽然我对其能否进行本地化表示怀疑,但发现其支持超乎想象。特别是与Hibernate相关的JDBC等,我一度怀疑本地编译之类的操作会很复杂,但结果完美运行。这可真让人吃惊啊。。。

对于本地构建,虽然存在内存和编译时间的限制,但如此轻量级的操作,有什么不可接受的呢?!

我把以上成果上传到了GitHub,请您查阅~

    quarkus-examples/tree/hibernate-graphql| github.com

→ 由于启动方法不太理想,我已对配置进行了更改。详细信息请参阅以下文章:”【2019年度11月版】跨多个docker-compose解决容器名称解析问题”!

今天就到这里!

广告
将在 10 秒后关闭
bannerAds