vlambda博客
学习文章列表

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

Chapter 10: Microservices Architecture with GraalVM

在前面的章节中,我们了解了 GraalVM 如何构建在 Java VM 之上并提供高性能的多语言运行时。在本章中,我们将探讨 GraalVM 如何成为运行微服务的核心运行时。许多微服务框架已经在 GraalVM 上运行。我们将探索一些流行的框架并用它们构建一个示例应用程序。我们还将探索无服务器框架。我们将进行案例研究,看看我们如何构建解决方案。

在本章结束时,您将很好地理解如何将应用程序打包为容器、运行 GraalVM,以及如何使用 Micronaut、Quarkus 和 Spring Boot 构建微服务应用程序。本章希望您对 Java 编程语言有一定的了解,并对构建 Java 微服务有一定的了解。

在本章中,我们将介绍以下主题:

  • An overview of GraalVM microservices architecture
  • An understanding of how GraalVM helps to build microservices architecture
  • Building microservices applications
  • A case study to help understand how to go about solutioning a microservices application built on GraalVM
  • Implementing a microservice with Spring Boot, Micronaut, Quarkus, and the Fn Project serverless framework

Technical requirements

本章提供了构建 Java 微服务的实践指南。这需要安装和设置一些软件。以下是先决条件列表:

那么,让我们开始吧!

Overview of microservices architecture

微服务是最流行的架构模式之一,已被证明是云原生应用程序开发的最佳架构模式。微服务模式有助于将应用程序分解和结构化为更小、可管理且自包含的组件,这些组件通过标准服务接口公开功能。以下是微服务架构模式的一些优点:

  • Loose coupling: Since the application is decomposed into services that provide a standard interface, the application component can be independently managed, upgraded, and fixed without affecting the other dependent components. This helps in easily changing the application logic based on growing business needs and changes.
  • Manageability: Since the components are self-contained, it is very easy to manage these applications. The components can be owned by smaller squads for development and can be deployed independently without deploying the whole application. This assists with rapid development and deployments using DevOps.
  • Scalable: Scalability is one of the key requirements of cloud-native applications. Scalability in monoliths is an issue, as we have to scale the whole application, even though we just need to scale some part of the functionality. For example, during high demands, we might want to scale the ordering, shopping cart, and catalog services more than any other functionality of a retail portal. That is not possible in monoliths, but if these components are decomposed into independent microservices, it's easy to scale them individually and set autoscale parameters so that they scale based on demand. This helps in utilizing cloud resources more effectively, at a lower cost.

现在让我们探索 GraalVM 如何帮助构建高性能微服务架构。  

Building microservices architecture with GraalVM

GraalVM 非常适合微服务 架构,因为它有助于构建占用空间更小的高性能 Java 应用程序。微服务架构最重要的要求之一是占用空间更小,启动速度更快。 GraalVM 是在云中运行多语言工作负载的理想运行时。 市场上已经有一些云原生的框架可以构建应用程序以在 GraalVM 上以最佳方式运行,包括 Quarkus、Micronaut 、 Helidon 和春天。

Understanding GraalVM containers

传统上,应用程序部署在为应用程序运行而预先配置和设置的基础设施上。基础架构由硬件和运行应用程序的软件平台组成。例如,如果我们必须运行一个 Web 应用程序,我们必须首先设置操作系统(例如 Linux 或 Windows)。 Web 应用程序服务器(Tomcat、WebSphere)和数据库(如 MySQL、Oracle 或 DB2)设置在预定义的硬件基础架构上,然后将应用程序部署在这些 Web 应用程序服务器之上。这需要很多时间,而且每次我们必须设置应用程序时,我们可能都必须重复这种方法。

为了减少设置时间并使配置更易于管理,我们通过预先打包应用程序以及各种平台组件(应用程序服务器、数据库等)和操作系统,集成到独立的虚拟机(VM)中。 (不要将这些 VM 与 Java 虚拟机 (JVM) 混淆。JVM 更像是一个运行平台Java 应用程序。在这种情况下,VM 不仅仅是一个应用程序平台。)

虚拟化帮助解决了很多配置和部署问题。它还允许我们通过在同一台机器上运行多个 VM 并更好地利用资源来优化硬件资源的使用。 VM 体积庞大,因为它们带有自己的操作系统,并且难以快速部署、更新和管理。

容器化通过引入另一层虚拟化解决了这个问题。大多数现代架构都建立在容器之上。容器是打包代码以及所有依赖项和环境配置的软件单元。容器是部署在容器运行时的轻量级、独立的可执行包。下图显示了 VM 和容器之间的区别:

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

图 10.1 – 虚拟机与容器

GraalVM 是一个完美的应用程序平台(尤其是当它被编译为原生代码时),可以与应用程序一起打包在同一个容器中。 GraalVM 提供 最小的占用空间和更快的启动和执行速度,以快速部署和扩展应用程序组件。

上图展示了如何使用 GraalVM 将应用程序容器化。在之前的模型中,每个容器都有自己的 VM,具有内存管理、分析、优化 (JIT) 等逻辑。 GraalVM 提供的是一个通用的运行时以及容器运行时,只是应用程序逻辑被容器化了。由于 GraalVM 还支持多种语言以及这些语言之间的互操作性,因此容器可以运行以多种语言编写的应用程序代码。

下图展示了如何使用 GraalVM 部署容器的各种场景:

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

图 10.2 – GraalVM 容器模式

在上图中,我们可以看到各种配置/场景。让我们来看看细节:

  • Container 1: In this container, we can see a native image running. This is by far the most optimal configuration with the smallest footprint and a faster load.
  • Container 2: In this container, we have a Java application and a JavaScript application running on Truffle with interoperability.
  • Container 3: Similar to container 2, we also can see a C/C++ application.

容器 1 是运行云原生的最佳配置,除非我们有使用不同编程语言编写的需要互操作的应用程序代码。另一种方法是编译原生镜像并将它们拆分到单独的容器中,并使用 REST 等标准协议进行交互。

这些容器可以使用各种编排器部署在云中,例如 Docker Swarm、Kubernetes(包括 Azure Kubernetes Service、AWS Elastic Kubernetes Service 和 Google Kubernetes Engine)、AWS Fargate 和 Red Hat OpenShift。

让我们借助一个案例研究如何将 GraalVM 用作微服务架构中的通用运行时。

Case study – online book library

要了解如何使用各种现代微服务框架在 GraalVM 上实现微服务,让我们通过一个非常简单的案例研究。在本章的后面,我们将从这个架构中挑选一个服务并使用不同的框架来构建它。

本案例研究涉及构建一个显示书籍目录的简单网站。目录列出了所有书籍。您可以通过特定的关键字搜索和浏览书籍,并且应该能够选择并获取与书籍相关的更多详细信息。然后,用户可以选择并将其保存为图书库中的愿望清单。将来,这可以扩展到订购这本书。但为简单起见,我们假设我们在 MVP(最小可行产品中搜索、浏览和创建个人库强>) 范围。让我们在目录中也有一个部分,用户可以在其中看到基于他们图书馆中的图书预测的图书。这也将帮助我们使用一些机器学习代码进行多语言处理。

Functional architecture

让我们来看看构建这个应用程序的思考过程。我们将首先分解功能。为此,我们将需要以下服务:

  • Catalogue UI Service: This web page is the home page where the user lands after successfully logging in (we will not be implementing the login, authentication, and authorization in MVP). This web page presents a way to search and view the books. This will be implemented as a micro-frontend (refer to https://micro-frontends.org/ for more details on micro-frontends). We will have three UI components as follows:

    一世。 图书列表 UI 组件:此组件显示所有图书的列表。

    ii. 图书详情 UI 组件:此组件显示与所选图书有关的所有详情。

    iii. Predicted Books UI 组件:此组件显示根据图书馆中的图书预测的图书。

  • Library UI Service: This lists the books in your personal library and allows the user to add or delete books from this library.

现在,为了支持这些 UI 服务,我们需要存储、获取和搜索书籍的微服务。以下是我们需要的服务:

  • Catalogue Service: These services provide the RESTful APIs to browse, search, and view the book details.
  • Prediction Service: To demonstrate the polyglot feature of GraalVM, let's assume that we already have machine learning code that we have developed using Python, and that can predict the book, based on the books that are available in the library. We will embed this Python code in this Java microservice to demonstrate how GraalVM can help us to build optimized embedding polyglot applications.
  • Library Service: This service will provide all the restful APIs for accessing books in the library, as well as for adding and deleting them from the library.
  • Book Info Service: Let's decide to use the Google Books API (https://developers.google.com/books) to get all the details about the books. We will need a service that proxies the Google Books API. This will help us to manage the data that is coming from the Google Books API. This also provides a proxy layer, so that we can always switch to a different Book API service without changing the whole application.

现在我们需要存储来存储已添加到个人图书馆的图书信息并缓存关于书籍,以便更快地获取(而不是每次都调用 Google Books API)。为此,我们将需要以下数据服务:

  • User Profile Data: This stores the user profiles.
  • User Library Data: This stores the books that the particular user has selected for their library.
  • Book Cache Data: We will need to cache the book information so that we don't have to call the Google Books API for information that we have already fetched. This will not only improve performance; it will also reduce costs as the Google Books API may charge you for the number of calls made.

下图说明了这些组件如何协同工作:

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

图 10.3 – 图书库应用程序 – 功能架构

在构建最终架构时,我们已经做出了各种架构决策。让我们快速回顾一下:

  • Micro-frontends: We decided to make the UI components micro-frontends so that it's easier for us to manage and reuse the UI code. As we can see, both the catalog UI and library UI reuse the same components to render the list of books and show the book details. We are choosing ReactJS as this provides a very sound framework for micro-frontend implementation.
  • Embedding Python: We decided to reuse the Python code already built for prediction. We decided to embed that as part of our catalog service to provide an endpoint that will provide a list of predicted books. This will also help us to demonstrate the capabilities of polyglot. We will use the pure Java implementation of microservices, as most modern microservices frameworks do not support polyglot.
  • Serverless: We decided to render the book info service serverless as it does not need to keep the state; it just calls the Google Books API and passes the information.
  • Book information cache: We decided to use Redis to store the book information cache so that we don't have to go back to the Google Books API each time, thereby improving performance and reducing the cost of calling Google APIs.

现在让我们看看部署架构在 Kubernetes 上的样子。更多详情请参考https://kubernetes.io/关于 Kubernetes 如何编排容器并提供可扩展且高度可用的解决方案。以下部分假设您对 Kubernetes 有很好的了解。

Deployment architecture

容器 部署在 Kubernetes 集群上。 下图展示了这些容器在Kubernetes中的部署架构。这在任何云中都可以类似:

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

图 10.4 – 书库应用的 Kubernetes 部署架构

让我们更详细地了解 上图中使用的术语:

  • Ingress Service deployment: Ingress Service will be the entry to the cluster. This will be a Kubernetes deployment that has the inbound port as 8080, and the target port pointing to the cluster IP of the Catalogue UI page, which is the home page.
  • ClusterUIService deployment: This deployment has the reactjs implementation of the home page and the library page, which internally use the same set of reactjs components. This calls Library Service to get the information regarding the books stored in the personal library. This service also calls Catalog Service, which has all the REST endpoints to search and  browse the book's details.
  • LibraryService deployment: Library Service is implemented in Quarkus as a native image and provides the endpoints for accessing personal library information. This uses Library Data Service.
  • LibraryDataService deployment: Library Data Service is a PostgreSQL container that stores all the user profile and personal library information. It also uses a persistent volume so that when a node goes down, the information is stored.
  • CatalogueInfoService deployment: This deployment has the implementation of CatalogueInfoService in Quarkus native mode. This service provides the endpoints to search, browse, and get various details relating to the book. BookInfoService is used to get all the information pertaining to the book. CatalogueInfoService also uses the BookInfoCache service to fetch data that is already in cache.
  • BookInfoService deployment: This deployment has a serverless implementation service that fetches various book information from the Google Books API. This will be implemented using the fn project serverless framework running on GraalVM.
  • BookInfoCacheService deployment: This deployment is a Redis cache that caches all the book information, so as to avoid redundant calls to the Google Books API.

最终完成的源代码可以在 Git 存储库中找到。我们不会讨论源代码,只是为了更好地了解如何构建这些微服务,我们将选择BookInfoService 并在下一节中使用各种微服务框架来实现它。

Exploring modern microservices frameworks

有一些现代框架是围绕快速创建微服务而构建的。这些框架建立在Container-First和Cloud-First设计原则的基础上。它们是从头开始构建的,具有快速启动时间和低内存占用空间。 Helidon、Micronaut 和 Quarkus 是使用最广泛的三个现代 Java 框架。这三个框架都在 GraalVM 上原生运行。这些框架中的每一个都承诺更快的启动和更低的内存占用,并且它们通过不同的方法来实现这一点。让我们在本节中探讨这些框架。

为了理解这些框架,现在让我们动手构建一个简单的图书信息服务。这是一个接受关键字的简单服务,使用 Google Books API 检索图书信息,并返回与 匹配关键字的所有图书相关的详细信息。响应返回为 JSON (JavaScript Object Notation – 参考 https://www.json.org/json-en.html 了解更多详情)。

让我们首先从我们使用 Spring Boot 没有 GraalVM 构建的传统微服务开始

Building BookInfoService using Spring without GraalVM

Spring 是使用最广泛的Java微服务框架之一。它具有许多出色的功能,是用于构建云原生 应用程序的流行框架之一。在这个部分,我们将在没有GraalVM的情况下以传统方式构建,以了解传统方式的缺点。

Creating Spring boilerplate code

要创建 Spring 样板代码,让我们在浏览器上转到 https://start.spring.io/。这个网站帮助我们指定一些配置和生成样板代码。让我们为 BookInfoService 生成样板代码。以下屏幕截图显示了 Spring 初始化程序:

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

图 10.5 – 生成样板代码的 Spring Initializr 屏幕截图

前面的屏幕截图显示了选择用于生成样板的配置。为了保持简单和集中,我们选择 Spring Web。我们将使用一个简单的 HttpClient 来调用 Google API 以保持对 ou 的简单性,而不是推荐的使用 jsonb 等方式。

我们需要提取生成的 ZIP 文件,然后实现服务。以下是核心逻辑代码片段。完整代码可在 Git 存储库中获得,地址为 https://github.com/PacktPublishing /Optimizing-Application-Performance-with-GraalVM:

@RestController

公共类 BookInfoServiceController {

    @RequestMapping("/book-info")

    public String bookInfo(@RequestParam String query) {

在前面的代码中,我们将路径设置为 /book-info 以便调用 BookInfoService。在以下代码中,我们将调用 Google API 来获取图书信息:

        字符串 responseJson ="{}";

        试试{

            String url = "https://www.googleapis.com/                 books/v1/volumes?q="+query+"&key=<你的谷歌                     api键>";

            HttpClient客户端=HttpClient.newHttpClient();

            HttpRequest请求=HttpRequest.newBuilder()                 .uri(URI.create(url)).build();

            HttpResponse<String>回复;

            响应=                  client.send(request, BodyHandlers.ofString());

            responseJson = response.body();

        } 捕获(异常 e){

            responseJson = "{'error','"+e.getMessage()+"'}";

            e.printStackTrace();

        }

        返回响应Json;

    }

}

在前面的代码中,我们使用 Google API 密钥调用 Google Books API。您必须获取自己的密钥并将其包含在 URL 中。有关详细信息,请参阅 https://cloud.google.com/apis/docs/overview有关如何获取自己的 Google API 的详细信息。我们使用 HttpClient 调用 Google Books API 并将响应传递给请求者。

现在让我们构建这段代码并运行它。我们将使用 Maven 来构建它。以下命令将构建代码:

./mvnw 包

这将下载所有依赖项、构建应用程序并生成 JAR 文件。您将在目标文件夹下找到 JAR 文件。我们应该能够使用以下命令运行 JAR 文件:

java -jar 目标/book-info-service-0.0.1-SNAPSHOT.jar

这将启动 Spring Boot 应用程序。以下屏幕截图显示了运行应用程序的输出:

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

图 10.6 – Spring BookInfoService 应用程序的输出截图

现在,让我们使用 REST 客户端访问 这个应用程序。在这个 案例中,我们使用 CocoaRestClient (https://mmattozzi.github.io/cocoa-rest-client/)。您可以使用任何 REST 客户端,甚至可以使用浏览器来调用服务。让我们调用 http://localhost:8080/book-info?query=graalvm。以下屏幕截图显示了输出:

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

图 10.7 – 调用 BookInformationService Spring 应用程序的输出

现在我们知道应用程序正在运行,让我们将这个应用程序打包到一个 Docker 容器 并构建镜像。以下是构建镜像的 Dockerfile 代码:

FROM 采用openjdk/openjdk11:ubi

ARG JAR_FILE=目标/*.jar

复制 ${JAR_FILE} bookinfo.jar

入口点 ["java","-jar","/bookinfo.jar"]

这是一个非常简单的 Dockerfile。我们正在使用 openjdk11 作为基础构建图像。然后,我们将复制我们生成的 jar 文件,并在启动容器时指定运行 jar 文件的入口点。现在让我们使用以下命令构建 Docker 映像:

docker build -t abvijaykumar/bookinfo-traditional 。

请随时为 Docker 映像使用您的名称标签。这些 Docker 镜像也可以在作者的 Docker Hub 上获得,网址为 https://hub.docker.com/u/abvijaykumar 。这将构建一个图像。我们应该能够使用以下命令查看镜像是否已创建:

码头工人图像

让我们使用以下命令运行 此图像:

docker run -p 8080:8080 abvijaykumar/bookinfo-traditional

以下屏幕截图显示了运行上一个命令的输出:

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

图 10.8 – 运行 BookInformationService Spring 应用程序的控制台输出

我们可以看到它在 2.107 秒内启动。我们应该能够调用该服务。以下屏幕截图显示了调用 http://localhost:8080/book-info?query=graalvm 后的输出:

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

图 10.9 – 在容器中调用 BookInformationService Spring 应用程序的结果

现在让我们使用现代框架构建相同的服务,以了解和比较这些现代框架如何在 GraalVM 中表现得更好。

Building BookInfoService with Micronaut

Micronaut 是由 Grails 框架的开发者推出的全栈微服务框架。它与所有生态系统和工具集成,并依赖于编译时集成,而不是运行时集成。这使得最终的应用程序运行得更快,因为它们在构建时编译了所有依赖项。它通过在构建时注入代码的注解和面向方面的编程概念来实现这一点。这是 2018 年推出的。有关 Micronaut 的更多详细信息,请参阅 https://micronaut.io/

让我们用 Micronaut 构建 BookInfoService。首先,我们需要安装 Micronaut 命令行。有关安装 Micronaut CLI 的详细说明,请参阅 https://micronaut.io/download/。安装后,我们应该可以调用 mn 命令了。现在让我们使用 mn 创建我们的 BookInfoService Micronaut 样板代码。以下命令创建样板代码。我们传递 -b=maven 标志来创建 Maven 构建:

  mn create-app com.abvijay.f.mn.bookinfoservice -b=maven

|在 /Users/vijaykumarab/Google Drive/GraalVM-Book/Code/chapter9/mn/bookinfoservice 创建的应用程序

我们应该会看到创建了一个名为bookinfoservice的目录,其中创建了所有生成的样板代码。现在让我们将 环境设置为指向 GraalVM。要验证我们是否使用了正确版本的 GraalVM,我们可以通过运行 java-version 进行检查。以下输出显示了 GraalVM 的版本:

java版本

java版本“11.0.10” 2021-01-19 LTS

Java(TM) SE 运行时环境 GraalVM EE 21.0.0.2 (build 11.0.10+8-LTS-jvmci-21.0-b06)

Java HotSpot(TM) 64 位服务器 VM GraalVM EE 21.0.0.2(内部版本 11.0.10+8-LTS-jvmci-21.0-b06,混合模式,共享)

现在让我们更新 Micronaut 代码来实现我们的逻辑。以下代码片段显示了 Controller 的代码,它公开了 REST 端点:

@Controller("/bookinfo")

公共类 BookInfoController {

    @Get("获取信息")

    public String getBookInfo(String query) {

        BookInfoService svc = new BookInfoService();

        String ret = svc.fetch(query);

        返回

    }

}

BookInfoService 类的代码与我们在前面代码中在 Spring Boot 中实现的代码完全相同。现在让我们通过执行以下命令来编译 Micronaut 项目:

./mvnw 包

我们可以然后通过执行以下命令来运行 Micronaut 应用程序:

./mvnw mn:运行

以下 屏幕截图显示了我们运行 Micronaut 应用程序时的输出

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

图 10.10 – 运行 Micronaut BookInformationService Spring 应用程序的控制台输出

我们可以看到加载 Micronaut 应用程序只用了 500 毫秒,而在 Building BookInfoService using Spring without GraalVM 部分中的 Spring Boot 大约需要 2 秒。考虑到我们的应用程序非常简单和小巧,这非常快。现在让我们构建这个应用程序的 Docker 映像。 Micronaut 通过传递 -Dpackaging=docker 参数提供了一种使用 Maven 构建 Docker 映像的直接方法。以下命令将直接生成 Docker 镜像:

mvn 包 -Dpackaging=docker

Micronaut 还可以生成 Dockerfile 以便我们可以单独自定义和执行。当我们将 -mn:dockerfile 参数传递给命令时,Dockerfile 会在目标目录下创建。以下是创建的 Dockerfile:

FROM openjdk:15-alpine

工作目录 /home/app

复制课程 /home/app/classes

复制依赖/* /home/app/libs/

曝光 8080

入口点 [“java”、“-cp”、“/home/app/libs/*:/home/app/classes/”、“com.abvijay.chapter9.mn.Application”]

我们可以看到Docker镜像是建立在openjdk之上的。我们仍然没有使用 GraalVM 原生镜像功能。让我们通过调用以下命令来构建这个镜像:

docker build -t abvijaykumar/bookinfo-micronaut 。

码头工人图像

现在让我们通过调用以下命令来运行这个 Docker 镜像:=

docker run -p 8080:8080 abvijaykumar/bookinfo-micronaut  

以下显示了运行上述命令的输出:

__&n​​bsp; __ _                        ;           _   

|  \/  (_) ___ _ __ ___  _ __   __ _ _   _| |_

| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __|

| |  | | | (__| | | (_) | | | | (_| | |_| | |_

|_|  |_|_|\__|_|  \___/|_| |_|\__,_|\__,_|\__|

  Micronaut (v2.4.1)

01:24:35.391 [main] INFO  io.micronaut.runtime.Micronaut - 启动在 1566 毫秒内完成。服务器运行:http://7df31221ee43:8080

我们可以看到应用程序在 1.5 秒内启动,这仍然比 Spring 图像快。我们仍然没有使用 GraalVM 原生镜像功能。现在让我们构建与 GraalVM 原生映像相同的 应用程序。为了构建 原生镜像,Micronaut 支持 Maven 配置文件,可以通过传递 -Dpackaging=native 来调用-image 命令的参数。以下命令创建本机映像:

./mvnw 包 -Dpackaging=native-image

码头工人图像

现在让我们生成 Dockerfile 来了解这个镜像是如何创建的。要生成 Dockerfile,我们需要执行以下命令:

mvn mn:dockerfile -Dpackaging=docker-native

这将在目标目录下生成 Dockerfile。以下代码显示了 Dockerfile:

FROM ghcr.io/graalvm/graalvm-ce:java11-21.0.0.2 AS builder

运行 gu 安装本机映像

工作目录 /home/app

复制课程 /home/app/classes

复制依赖/* /home/app/libs/

RUN native-image -H:Class=com.abvijay.chapter9.mn.Application -H:Name=application --no-fallback -cp "/home/app/libs/*:/home/app/classes/"

FROM frolvlad/alpine-glibc:alpine-3.12

运行 apk 更新和 apk 添加 libstdc++

复制 --from=builder /home/app/application /app/application

曝光 8080

入口点 ["/app/application"]

我们可以看到这是一个多阶段的Dockerfile。在第一阶段,我们安装原生镜像,将所有需要的应用程序文件复制到镜像中,最后运行native-image 命令来创建原生图像。在第二阶段,我们复制原生镜像并提供入口点。

让我们运行这个图像,看看它的加载速度有多快。让我们执行以下命令:

docker run -p 8080:8080 bookinfoservice

以下输出显示图像加载仅用了 551 毫秒,几乎是非 GraalVM Micronaut 应用程序所需时间的一半:

/app/application: /usr/lib/libstdc++.so.6: 没有可用的版本信息(/app/application 需要)

__&n​​bsp; __ _                        ;           _

|  \/  (_) ___ _ __ ___  _ __   __ _ _   _| |_

| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __|

| |  | | | (__| | | (_) | | | | (_| | |_| | |_

|_|  |_|_|\__|_|  \___/|_| |_|\__,_|\__,_|\__|

  Micronaut (v2.4.1)

09:16:19.604 [main] INFO  io.micronaut.runtime.Micronaut - 启动在 551 毫秒内完成。服务器运行:http://da2bf01c90e4:8080

我们可以看到使用 Micronaut 创建微服务是多么容易,以及它如何与 GraalVM 工具链无缝集成以生成占用空间非常小、加载速度快的 Docker 镜像。

Quarkus 是另一个非常流行的微服务框架。现在让我们探索 Quarkus 并使用 Quarkus 构建相同的服务。

Building BookInfoService with Quarkus

Quarkus 由 Red Hat 开发,提供最复杂的与 Java 框架生态系统的集成列表。它建立在 MicroProfile、Vert.x、Netty 和 Hibernate 标准之上。它构建为完全 Kubernetes 原生的 框架。这是在 2019 年推出的。

现在让我们使用 Quarkus 构建 BookInfoService。 Quarkus 在 http://code.quarkus.io 提供了一个起始代码生成器。让我们去那个网站并生成我们的代码。以下屏幕截图显示了为生成我们的 BookInfoService 样板代码而选择的配置。我们还包括 RESTEasy JAX-RS 来创建我们的端点:

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

图 10.11 – 生成样板代码的 code.quarkus.io 截图

这个 将在 zip 文件中生成代码(我们也可以提供一个 Git 存储库,Quarkus 将自动推送代码)。现在让我们下载 zip 文件,然后使用以下命令提取并编译

./mvnw 编译 quarkus:dev。

关于 Quarkus 最好的 部分是,当我们执行这个命令时,它为我们提供了一种无需重新启动服务器即可编辑和测试代码的方法。这有助于快速构建应用程序。现在,让我们将 Quarkus 代码更新到我们的 BookInfoService 端点。

以下代码显示了端点的实现:

@Path("/bookinfo")

公共类 BookInfoService {

    @GET

    @Produces(MediaType.TEXT_PLAIN)

    @Path("/getBookInfo/{query}")

    public String getBookInfo(@PathParam String query) {

        字符串 responseJson = "{}";

        试试{

            String url = "https://www.googleapis.com/books/                 v1/volumes?q="+查询           ;          + "andkey=<你的 google api 密钥>";

            HttpClient客户端=HttpClient.newHttpClient();

            HttpRequest请求=             ;    HttpRequest.newBuilder()                     .uri(URI.create(url)).build();

            HttpResponse<String>回复;

            response = client.send(request,                 BodyHandlers.ofString());

            responseJson = response.body();

        } 捕获(异常 e){

            responseJson =                  "{'error', '" + e.getMessage() + "'}";

            e.printStackTrace();

        }

        返回响应Json;

    }

}

当我们更新代码并保存它时,Quarkus 会自动更新运行时。我们不必重新启动服务器。以下屏幕截图显示了调用 Quarkus 运行的 bookservice 的输出:

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

图 10.12 – 调用 BookInformationService 应用程序的 Quarkus 实现的结果

现在让我们使用 Quarkus 构建 一个 GraalVM 原生镜像。为此,我们需要编辑 pom.xml 文件并确保我们具有以下配置文件:

<个人资料>

    <简介>

        <id>原生</id>

        <属性>

            <quarkus.package.type>native</quarkus.package.type>

        </properties>

    </profile>

</个人资料>

Quarkus 使用 Mandrel,它是 GraalVM 的下游发行版。您可以在 Mandrel 的更多信息of-graalvm-for-the-red-hat-build-of-quarkus/" target="_blank">https://developers.redhat.com/blog/2020/06/05/mandrel-a-community-distribution -of-graalvm-for-the-red-hat-build-of-quarkus/。

现在让我们构建原生镜像。 Quarkus 提供了一个直接的 Maven 配置文件来构建原生镜像。我们可以通过执行以下命令来创建原生镜像:

mvn 包 -Pnative

这将在目标文件夹下创建本机构建。让我们直接运行原生构建。下面是运行原生镜像后的输出:

./bookinfoservice-1.0.0-SNAPSHOT-runner

__&n​​bsp; ____  __  _____   ___  __ ____  ______

--/ _​​_ \/ / / / _ | / _ \/ //_/ / / / __/

-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   

--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   

2021-03-31 11:26:31,564 INFO  [io.quarkus] (main) bookinfoservice 1.0.0-SNAPSHOT native(由 Quarkus 1.13.0.Final 提供支持)在 0.015 秒内启动。收听:http://0.0.0.0:8080

2021-03-31 11:26:31,843 信息  [io.quarkus](主要)配置文件产品已激活。

2021-03-31 11:26:31,843 INFO  [io.quarkus](主要)安装的功能:[cdi、rest-client、rest-client-jsonb、resteasy]

我们可以看到启动应用程序只用了 0.015s。这比传统的实现要快得多,后者需要大约 2 秒才能启动。

Quarkus 还创建了各种 Dockerfile 版本,我们可以在 Docker 文件夹下找到这些版本。 以下屏幕截图显示了 Quarkus 自动创建的 Dockerfile 的 列表:

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

图 10.13 – Quarkus 创建的各种版本的 Dockerfile 的屏幕截图

让我们快速探索这些不同类型的 Dockerfile:

  • Dockerfile.legacy-jar and Dockerfile.jvm: This Dockerfile has the commands to build a Docker image with a normal Quarkus application, JAR, and OpenJDK headless.
  • Dockerfile.native: This Dockerfile builds the native image.
  • Dockerfile.native-distroless: This Docker file also generates an image with a native image, but uses the new technique introduced by Google to build the image that contains just the application, language runtime, and no operating system distribution. This helps in creating a small image, and has fewer vulnerabilities. Refer to https://github.com/GoogleContainerTools/distroless for more details on distroless containers.

我们可以通过执行以下命令来创建这些不同 Docker 版本的 Docker 镜像:

docker build -f ./src/main/docker/Dockerfile.jvm -t abvijaykumar/bookinfo-quarkus-jvm .

docker build -f ./src/main/docker/Dockerfile.jvm-legacy -t abvijaykumar/bookinfo-quarkus-jvm-legacy .

docker build -f ./src/main/docker/Dockerfile.native -t abvijaykumar/bookinfo-quarkus-native .

docker build -f ./src/main/docker/Dockerfile.native-distroless -t abvijaykumar/bookinfo-quarkus-native-distroless .

比较这些图像的大小,让我们运行以下命令:

码头工人图像

下表比较了每个图像的大小:

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

图 10.14 – 比较 Docker 镜像大小的图表

写这本书的时候,最小占用空间和最快执行的GraalVM微服务镜像是使用Quarkus构建的原生 distroless 镜像。 Spring 还推出了 Spring Native (https://spring.io/blog/2021/03/11/announcing-spring-native-beta) 和 Oracle 有 Helidon (https://helidon.io/#/),它提供了在GraalVM上运行的类似框架。

Building a serverless BookInfoService using fn project

功能即服务或无服务器是另一种用于按需运行代码并利用云资源的架构模式。无服务器方法在收到请求 时运行代码。代码启动、执行、处理请求,然后关闭,从而最大限度地利用云资源。这以最优成本提供了高度可用、可扩展的架构。但是,无服务器架构需要更快的启动、更快的执行和关闭。

GraalVM 原生镜像(提前)是无服务器的最佳选择,因为原生镜像的启动和运行速度比传统 Java 应用程序更快。 GraalVM 原生镜像占用空间非常小,启动速度快,并且带有嵌入式 VM(Substrate VM)。

fn 项目也是构建无服务器应用程序的绝佳环境。 Fn 支持在 Go、Java、JavaScript、Python、Ruby 和 C# 中构建无服务器应用程序。它是一个非常简单和快速的应用程序开发环境,带有一个 fn 守护程序和一个 CLI,它提供了构建无服务器应用程序的大部分脚手架。

在这个部分,让我们专注于使用 fn 项目构建 BookInfoService 函数。请参考https://fnproject.io/了解详细说明关于安装 fn 命令行界面。我们首先必须使用 fn start 启动 fn 守护程序服务器。 fn 服务器在 Docker 中运行,您可以通过运行 docker ps 进行检查。 Fn 守护程序服务器在端口 8080 上运行。

fn 命令行还提供了一种生成样板代码的方法。现在让我们通过执行以下命令来生成项目:

fn init --runtime java book-info-service-function

在以下位置创建函数:./book-info-service-function

生成函数样板。

func.yaml 创建。

这将创建一个包含所有样板代码的 book-info-service-function 目录。让我们检查一下该目录中的内容。我们将找到 func.yaml、pom.xml 和 src 目录。

func.yml 是主清单 yaml 文件,其中包含有关实现函数的类和入口点的关键信息。让我们检查配置文件:

schema_version:20180708

名称:book-info-service-function

版本:0.0.1

运行时:java

build_image: fnproject/fn-java-fdk-build:jdk11-1.0.124

run_image: fnproject/fn-java-fdk:jre11-1.0.124

cmd: com.example.fn.HelloFunction::handleRequest

现在让我们了解前面的配置文件:

  • name: The name of the function. We can see the name of the function that we specified in our fn init command line.
  • version: The version of this function.
  • runtime: JVM as the runtime.
  • build_image: The Docker image that should be used to build the Java code; in this case, we see that it's JDK 11.
  • run_image: The Docker image that should be used as a runtime; in this case, it is JRE11.
  • cmd: This is the entry point, which is ClassName:MethodName. We will change cmd to point to our class and method: cmd: com.abvijay.chapter9.fn.BookInfoService::getBookInfo.

src 文件夹中,我们将使用 getBookInfo( ) 方法。 getBookInfo() 的实现与我们之前在本节中执行的其他实现相同。

以下代码显示了调用 Google API 以获取书籍的函数的实现:

    public String getBookInfo(String query) {

        字符串 responseJson = "{}";

        试试{

            String url = "https://www.googleapis.com/                 books/v1/volumes?q="+查询           ;          + "&key=<your_google_api_key>";

            HttpClient客户端=HttpClient.newHttpClient();

            HttpRequest请求=HttpRequest.newBuilder()                 .uri(URI.create(url)).build();

            HttpResponse<String>回复;

            response = client.send(request,                 BodyHandlers.ofString());

            responseJson = response.body();

        } 捕获(异常 e){

            responseJson =                  "{'error', '" + e.getMessage() + "'}";

            e.printStackTrace();

        }

        返回响应Json;    

    }

现在让我们构建并部署这个无服务器容器到本地Docker。功能被分组到应用程序中。一个应用程序可以有多种功能。这有助于对它们进行分组和管理。所以我们需要使用 fn create app 命令创建一个图书信息服务应用。下面显示了执行命令后的输出:

fn 创建应用 book-info-service-app

成功创建应用程序:  book-info-service-app

创建应用后,我们可以使用 fn deploy 命令对其进行部署。此命令必须在我们创建的函数应用程序的根文件夹中执行。下面显示了执行命令后的输出:

fn deploy --app book-info-service-app --local

将 book-info-service-function 部署到应用程序: book-info-service-app

升级到 0.0.2 版

构建镜像 book-info-service-function:0.0.2 ..................................... ..................................................... ……………………………………………………………………………………………………………………

使用图像 book-info-service-function:0.0.2 更新功能 book-info-service-function...

成功创建函数:book-info-service-function 和 book-info-service-function:0.0.2

fn deploy 命令将使用 Maven 构建代码,将其打包为 Docker 映像,并将 部署到本地 Docker 运行时。 fn 也可用于直接部署到云端或 k8s 集群。

现在让我们使用 docker images命令检查我们的镜像是否已经构建:

码头工人图像

我们可以也可以使用fn inspect来获取函数的所有细节。这有助于发现服务。下面显示了执行命令的输出:

fn 检查功能 book-info-service-app book-info-service-function

{

    “注释”:{

        "fnproject.io/fn/invokeEndpoint":

        "http://localhost:8080/invoke/       ;  01F29E8SXKNG8G00GZJ0000002"

    },

    "app_id": "01F29E183WNG8G00GZJ0000001",

    "created_at": "2021-04-02T14:02:25.587Z",

    "id": "01F29E8SXKNG8G00GZJ0000002",

    "idle_timeout": 30,

    “图像”:“图书信息服务功能:0.0.2”,

    “记忆”:128,

    "name": "book-info-service-function",

    “超时”:30,

    “updated_at”:“2021-04-02T14:02:25.587Z”

}

现在让我们调用该服务。由于 我们的函数需要 数字中的输入参数,我们可以使用 echo 命令传递它并将输出通过管道传送到 < strong>fn invoke 调用我们的函数:

回声-n 'java' | fn 调用 book-info-service-app book-info-service-function

{

  "kind": "books#volumes",

  “项目总数”:1941,

  “项目”:[

    {

        "kind": "books#volume",

        "id": "Q3_QDwAAQBAJ",

        "etag": "0Hl3HzInpzY",

        "selfLink": "https://www.googleapis.com/           ;  books/v1/volumes/Q3_QDwAAQBAJ",

                "volumeInfo": {

        "title": "Java 性能",

我们可以看到正在执行的函数和 Google API 的输出(前面的输出是部分的,以节省空间)。现在让我们在 GraalVM 上运行相同的逻辑。

GraalVM 的基本 图像不同。我们使用 fnproject/fn-java-native-init作为基础,初始化我们的fn 项目。以下是生成 Graal 原生基于图像的 fn 项目的输出:

fn init --init-image fnproject/fn-java-native-init book-info-service-function-graal

在以下位置创建函数:./book-info-service-function-graal

运行 init-image:fnproject/fn-java-native-init

执行 docker 命令:运行 --rm -e FN_FUNCTION_NAME=book-info-service-function-graal fnproject/fn-java-native-init

func.yaml 创建。

这会生成一个 Dockerfile。接下来是 Dockerfile 代码,您可以在项目目录 (book-info-service-function-graal) 下找到该代码。此 fn 配置的工作方式不同。它还会生成一个 Dockerfile,其中包含所有必要的 Docker 构建命令。这是一个多阶段的 Docker 构建文件。让我们检查一下这个 Dockerfile:

读书笔记《supercharge-your-applications-with-graalvm》第 10 章使用 GraalVM 的微服务架构

图 10.15 – fn 生成的 Dockerfile

让我们了解这个 Dockerfile:

  • Line 17: The image will be built using fnproject/fn-java-fdk-build.
  • Line 18: This sets the working directory to /function.
  • Lines 19–23: Then, the Maven environment is configured.
  • Lines 25–40: Using fnproject/fn-java-native as the base image, the GraalVM is configured and the fn runtime is compiled. This is a very important step as this is what makes our serverless runtime faster and with a smaller footprint.
  • Lines 43–47: The native images are copied using the busybox:glibc (which is the minimal version of Linux+glibc) base image.
  • Line 48: This is the function entry point. func.yml, in this way of building the serverless image, has no information. fn will use the Dockerfile to perform the build (along with Maven) and deploy the image to the repository.

我们需要更改 line 48 以指向我们的类。让我们用以下内容替换它:

CMD ["com.abvijay.chapter9.fn.BookInfoService::BookInfoService"]

我们需要更改的另一个重要配置文件是 reflection.json  在 src/main/conf。 这个 JSON 文件包含有关类名和方法。本地图像构建器使用它来解决我们通过动态调用我们的函数所做的反射。请参阅第 5 章中的构建原生图像部分,Graal Ahead-of-Time编译器和原生图像

现在,让我们创建一个 fn 应用并使用 fn create app 命令部署该应用。以下是执行命令后的输出:

fn 创建应用 book-info-service-app-graal

成功创建应用程序:  book-info-service-app-graal

我们可以构建原生镜像并使用fn deploy –app book-info-service-app-graal命令进行部署,我们可以执行该方法通过调用 echo -n 'java' | fn 调用 book-info-service-app-graal book-info-service-function。查看Docker 镜像,我们会看到Java镜像大小为238MB,GraalVM镜像大小为仅 41 MB。这比传统 Java 应用程序占用的空间小 10 倍。我们可以对函数调用进行计时,我们可以看到原生图像要快得多(高达 30%)。

码头工人图像

无服务器是最好的解决方案,可以提供更快的无状态服务,因为它不占用任何资源,我们也不必让它一直运行。

在本节中,我们研究了各种框架实现和优化图像的方法。

Summary

恭喜你达到了这一点!在本章中,我们研究了微服务架构是如何构建的。为了理解架构思维过程,我们选择了一个简单的案例研究,并探讨了如何将其作为微服务集合部署在 Kubernetes 上。然后我们探索了各种微服务框架,并在每个框架上构建了一个服务,以了解 GraalVM 为云原生架构带来的好处。

阅读本章后,您应该已经很好地理解了如何使用 GraalVM 作为运行时构建基于微服务的云原生应用程序。本章为 Java 开发人员快速开始在其中一个微服务框架(Quarkus、Spring、Micronaut)上构建应用程序提供了良好的开端。与本章一起提供的源代码(在 Git 中)也将为 GraalVM 上的微服务提供一个很好的参考实现。

Questions

  1. What is a microservice?
  2. What are the advantages of a microservices architecture?
  3. Why is GraalVM an ideal application runtime for microservices?