使用 Micronaut和OpenFaaS 构建无服务器Java 应用程序 - openvalue


在Java生态系统中的微服务上工作时,尤其是使用Spring(Boot)时,您会注意到应用程序会有很长的启动时间,更不用说它们将拥有高内存消耗了。每个微服务的开销最终将在系统上承担其成本。而诸如Micronaut之类的框架可以帮助减少这种开销,而又不损失任何开发人员的生产力。使用Micronaut不仅可以构建“经典”应用程序,而且可以使用OpenFaaS在云环境或Kubernetes上构建和部署无服务器应用程序和功能。让我们开始吧!
小注释:在本文中,对Spring进行了一些参考和比较。因此,有关Spring的一些知识可能会很有用。

隆重介绍Micronaut
Micronaut是一个基于JVM的框架,旨在构建模块化微服务。当您打开Micronaut项目时,乍看之下不会感到惊讶,它的外观和感觉与Java世界中常见的Spring(引导)项目相同。但是,差异远远很小或微妙,在开始将此类项目投入生产之前,应该很好地理解它们。另一方面,开始发现Micronaut世界的过程将非常顺利,并且随着您逐步探索Micronaut世界,对它的理解也会随之增长。

注释和Micronaut
众所周知,在运行时Spring会使用大量反射,而Micronaut所做的这些工作却是编译时已经完成。一个示例是运行时生成的Spring Data查询,Micronaut将在编译时生成相同的查询,从而在运行时最大程度地减少使用的内存。创建的每个Bean都会在编译时得到充实,从而创建一个所谓的BeanDefinition 类,其中包含Bean的需求及其构造函数。所有这些类都使用进行处理BeanDefinitionInjectProcessor。
而且,Micronaut依赖于Java EE依赖注入,因此可以使用@Singleton和使用@Inject对Bean进行注释 。这些bean的生命周期将由Micronaut自己管理。

构建一个Micronaut组件
对于与Micronaut有关的所有事情,都可以使用CLI(命令行界面)工具。只需按照官方文档中的步骤安装即可 。
对于使用Micronaut的任何项目,CLI工具都是一个很好的起点,它将生成项目的主干结构以及一些有用的开发文件。它为您完成整个脚手架。对于每种应用程序类型,可以设置功能列表以生成这些功能所需的所有必要配置。CLI工具支持三种JVM语言:Java,Kotlin和Groovy,以及两种构建工具:Maven和Gradle。如果是Maven项目,则将生成Maven包装器以及pom.xml。
紧接着,CLI工具将生成一个Micronaut-cli.yml,这将是CLI工具进行任何进一步操作的输入,并将包含项目的名称和配置文件。
如果你来自Spring世界,会在Spring 的main / resources目录中找到:application.yml。就像在Spring应用程序中一样,此文件包含该应用程序的所有配置设置。

示例
一个简单的应用程序将为作者提供一本书。为此,它将调用一个返回作者所有书籍的函数。
在此示例中,应用程序将包含一个REST端点,以询问特定作者的书。该端点必须调用一个函数来检索此信息。因此,该应用程序将是一个http服务器以及一个http客户端,并将部署在Kubernetes上。
因此,生成应用程序基础的命令将是:

mn create-app bookstore-service --build=Maven --lang=java --features=Kubernetes,netflix-hystrix,http-server,http-client

由于该服务调用一个函数,因此需要netflix-hystrix。一个函数需要一些时间才能启动(预热时间)。这并不意味着会花费很多时间,但是HTTP调用肯定会花费一些时间。为避免该函数直接返回状态为500的HTTP响应,需要一种重试机制以确保在答案可用后立即对其进行检索。
由于该应用程序将按函数指示部署在Kubernetes上Kubernetes,因此该create-app命令还将生成一个k8s.yml,它将作为部署的基础。当然,必须根据部署环境的要求对其进行调整。该Kubernetes配置将具有部署和服务。
默认情况下,此应用程序将在端口8080上运行。要更改application.yml中的以下属性,可以将其设置为首选值:

   micronaut:
        server:
            port: 8081

现在是时候添加一个端点来检索给定作者的所有书籍。为此,将使用Micronaut的HTTP函数。

 

package bookstore.service.store;

    import io.Micronaut.http.annotation.Controller;
    import io.Micronaut.http.annotation.Get;
    import io.Micronaut.http.annotation.PathVariable;

    @Controller("books")
    public class BookstoreController {

        @Get(
"/{author}")
        public Book retrieveBooksByAuthor() {
            return new Book(
"1000 new things", "John Doe");
        }

    }

这看起来很像Spring Controller,对吗?除了使用Micronaut软件包中的注释外。
这里引人注目的是BookDTO 的实现:

 

@Introspected
    public class Book implements Serializable {

        private String title;
        private String author;

        public Book(String title, String author) {
            this.title  = title;
            this.author = author;
        }

        // some getters
    }

@Introspected是反射DTO所需的注释。在编译时,将执行检查以查看是否可以为DTO初始化所有属性。
构建一个函数来服务需要快速提供给我们应用程序的数据,而无需使用大量逻辑。在我们的示例中,该函数将返回给定作者姓名的书籍清单。让我们从最简单的情况开始:

mn create-function get-books-by-author --build=Maven --features=openfaas

为了使该函数可构建,必须删除一个AWS依赖项(在本示例中,将不使用任何AWS功能,并且它将返回类路径错误),我们是为在OpenFaaS上构建运行的函数。:

   <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-lambda-java-log4j2</artifactId>
        <version>1.0.0</version>
        <scope>runtime</scope>
    </dependency>

在撰写本文时,Dockerfile仍将存在一个错误。Dockerfile基于OpenJDK 8映像,但它应基于OpenJDK 13映像,以免遇到任何运行时/编译问题。
在Dockerfile中的以下代码行中,必须删除两个标志:

ENV fprocess="java -Dcom.sun.management.jmxremote -noverify -XX:TieredStopAtLevel=1 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -jar Handler.jar"

-noverify和-XX:+UseCGroupMemoryLimitForHeapJDK 13中已弃用,因此不需要。代码行将变为:

ENV fprocess="java -Dcom.sun.management.jmxremote -XX:TieredStopAtLevel=1 -XX:+UnlockExperimentalVMOptions -jar Handler.jar"

接下来,我们需要添加log4j2.xml作为log4日志记录的配置。
此外,还必须为jar中的日志添加以下内容:

   <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
        <mainClass>${exec.mainClass}</mainClass>
        <manifestEntries>
            <Multi-Release>true</Multi-Release>
        </manifestEntries>
    </transformer>

现在,为方便起见,仅通过在Dockerfile中进一步更改fprocess命令,将日志记录级别设置为ERROR:

ENV fprocess="java -Dorg.apache.logging.log4j.simplelog.StatusLogger.level=ERROR -Dcom.sun.management.jmxremote -XX:TieredStopAtLevel=1 -XX:+UnlockExperimentalVMOptions -jar Handler.jar"

为了使函数尽可能简单,函数类不会扩展 FunctionInitializer.

部署Micronaut
现在需要一个环境来部署它。OpenFaaS是在Kubernetes上部署应用程序或功能的不错选择。对于Micronaut功能,OpenFaaS将部署一个Pod。在此Pod上,直到调用终结点之前,任何内容都不会运行。届时,应用程序将开始运行并返回端点调用。

安装OpenFaaS非常容易,不应花费太多时间。在执行项目时,我想探索Micronaut功能。但是,不能选择部署到AWS,而我的好奇心是由OpenFaaS触发的。在本地安装OpenFaaS非常容易。OpenFaaS使在现有Kubernetes集群上轻松部署功能和应用程序变得容易。
此处 列出了安装说明 。
简而言之,从安装CLI开始:

brew install faas-cli

然后在安装OpenFaaS之前,安装Arkade(这是以后安装OpenFaaS的最快选择):

  curl -SLsf https://dl.get-arkade.dev/ | sudo sh

现在,您终于可以在本地Kubernetes集群上安装OpenFaaS了:

arkade install openfaas

现在的诀窍是不要忽略安装中出现的所有日志记录行,那里有一些有用的说明可以帮助您完成安装。首先要检查所有部署是否都成功:

 kubectl -n openfaas get deployments -l "release=openfaas, app=openfaas"

成功的部署应该是:

 NAME                READY   UP-TO-DATE   AVAILABLE   AGE

    alertmanager        1/1     1            1           6d21h

    basic-auth-plugin   1/1     1            1           6d21h

    faas-idler          1/1     1            1           6d21h

    gateway             1/1     1            1           6d21h

    nats                1/1     1            1           6d21h

    prometheus          1/1     1            1           6d21h

    queue-worker        1/1     1            1           6d21h

现在必须采取最后一步。OpenFaaS使用的网关应转发:

   kubectl -n openfaas rollout status deploy/gateway
    kubectl -n openfaas port-foward svc/gateway 8080:8080

不要忘记设置登录名:

PASSWORD=$(kubectl get secret -n openfaas basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode; echo)
    echo -n $PASSWORD | faas-cli login --username admin --password-stdin
                

OpenFaaS现在应该已经启动并运行!

部署Micronaut函数:在OpenFaaS上测试功能:OpenFaaS需要注册表来推送和部署功能。对于本地开发,可以在docker容器中启动注册表:

 sudo docker run -d -p 5000:5000 --name registry registry:2

在提供者描述(仅指定网关的端点)之后, 更仔细地看看function.yml,该功能被描述为必须运行的docker映像:

    provider:
        name: openfaas
        gateway: http://127.0.0.1:8080
    functions:
        get-books-by-author:
            lang: dockerfile
            handler: .
            image: localhost:5000/get-books-by-author:latest

在此示例中,该映像的前缀localhost:5000/是前一个bash命令启动的本地注册表。
现在,让我们使用的神奇命令faas-cli来部署和运行该函数:

    faas-cli build -f function.yml
    faas-cli push -f function.yml
    faas-cli deploy -f function.yml
                

现在通过OpenFaaS网关调用该函数:

curl -X GET http://127.0.0.1:8080/function/get-books-by-author -H 'Content-Type: application/json' -d $'{"name":"Piet"}'

要验证该功能正在运行:

kubectl -n openfaas-fn get pods

现在,此函数已经有运行的Pod,一旦对端点进行REST调用后,pod将启动应用程序。这是通过看门狗完成的 。

资源:
本文中的所有内容都是在以下环境下开发的:

  • Mac OSX Catalina
  • Docker桌面(Engine v.19.03.5,Kubernetes v1.15.5)
  • OpenJDK的13.0.2

参考文献: