使用 Spring Boot 和 grpc-server-spring-boot-starter 开发一个 gRPC 服务

大多数使用 REST 的系统间通信组件在 JSON 中序列化它们的有效负载。截至目前,JSON 缺乏广泛使用的模式验证标准:JSON Schema并不广泛。标准模式验证允许将验证委托给第三方库并使用它完成,我们必须退回到代码中的手动验证。更糟糕的是,我们必须保持验证代码与架构同步。

XML 具有开箱即用的模式验证:XML 文档可以声明它必须符合的语法。基于 XML 的 SOAP 也从中受益。

其他序列化备选方案具有模式验证选项:例如,Avro、Kryo 和 Protocol Buffers。有趣的是,gRPC 使用 Protobuf 跨分布式组件提供 RPC:

gRPC 是一个现代开源高性能远程过程调用 (RPC) 框架,可以在任何环境中运行。它可以通过对负载平衡、跟踪、健康检查和身份验证的可插拔支持,有效地连接数据中心内和跨数据中心的服务。它还适用于分布式计算的最后一英里,将设备、移动应用程序和浏览器连接到后端服务。

而且Protocol是一种二进制序列化机制,节省了大量的带宽。因此,gRPC 是系统间通信的绝佳选择。但是,如果您的所有组件都使用 gRPC,那么简单的客户端如何调用它们呢?在本文中,我们将构建一个 gRPC 服务并展示如何从 cURL 调用它。

一个简单的 gRPC 服务

gRPC 文档写的很详细,这里只写一个摘要:

  • gRPC 是一个远程过程调用框架。
  • 它适用于多种语言。
  • 它依赖于协议缓冲区:

Protocol buffers 是 Google 的语言中立、平台中立、可扩展的结构化数据序列化机制——想想 XML,但更小、更快、更简单。您定义了一次数据的结构化方式,然后您可以使用特殊生成的源代码轻松地将结构化数据写入各种数据流并使用各种语言从中读取结构化数据。-协议缓冲区

  • 它是CNCF产品组合的一部分,目前处于孵化阶段。

让我们设置我们的 gRPC 服务。我们将使用 Java、Kotlin、Spring Boot 和专用的 gRPC Spring Boot 集成项目。项目结构包含两个项目:一个用于模型,一个用于代码。让我们从模型项目开始。

我不想要复杂的东西。重用一个简单的例子就足够了:请求发送一个字符串,响应在它前面加上前缀Hello。我们在专用的 Protobuf 模式文件中设计此模型:

syntax = "proto3";                                        //1

package ch.frankel.blog.grpc.model;                       //2

option java_multiple_files = true;                        //3
option java_package = "ch.frankel.blog.grpc.model";       //3
option java_outer_classname = "HelloProtos";              //3

service HelloService {                                    //4
    rpc SayHello (HelloRequest) returns (HelloResponse) {
    }
}

message HelloRequest {                                    //5
    string name = 1;                                      //6
}

message HelloResponse {                                   //7
    string message = 1;                                   //6
}
  1. Protobuf 定义版本
  2. Package
  3. 特定于 Java 的配置
  4. 服务定义
  5. 请求定义
  6. 字段定义:首先是类型,然后是名称,最后是顺序。
  7. 响应定义

我们将使用 Maven 生成 Java 样板代码:

<project>
  <dependencies>
    <dependency>
      <groupId>io.grpc</groupId>                         <!--1-->
      <artifactId>grpc-stub</artifactId>
      <version>${grpc.version}</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>                         <!--1-->
      <artifactId>grpc-protobuf</artifactId>
      <version>${grpc.version}</version>
    </dependency>
    <dependency>
      <groupId>jakarta.annotation</groupId>              <!--1-->
      <artifactId>jakarta.annotation-api</artifactId>
      <version>1.3.5</version>
      <optional>true</optional>
    </dependency>
  </dependencies>
  <build>
    <extensions>
      <extension>
        <groupId>kr.motd.maven</groupId>                 <!--2-->
        <artifactId>os-maven-plugin</artifactId>
        <version>1.7.1</version>
      </extension>
    </extensions>
    <plugins>
      <plugin>
        <groupId>org.xolstice.maven.plugins</groupId>    <!--3-->
        <artifactId>protobuf-maven-plugin</artifactId>
        <version>${protobuf-plugin.version}</version>
        <configuration>
          <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
          <pluginId>grpc-java</pluginId>
          <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>compile-custom</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
  1. 编译时依赖
  2. 嗅探有关操作系统的信息;在下一个插件中使用
  3. 从文件生成 Java 代码proto。 

编译后,结构应如下所示:

编译后的结构

我们可以将这些类打包到 JAR 中并在 Web 应用程序项目中使用它。后者在 Kotlin 中,但这只是因为它是我最喜欢的 JVM 语言。

我们只需要一个特定的 Spring Boot 启动器依赖项来将 gRPC 端点与 Spring Boot 集成:

XML:

<dependency>
  <groupId>net.devh</groupId>
  <artifactId>grpc-server-spring-boot-starter</artifactId>
  <version>2.14.0.RELEASE</version>
</dependency>

这是重要的一点:

Kotlin

@GrpcService                                                        //1
class HelloService : HelloServiceImplBase() {                       //2
  override fun sayHello(
      request: HelloRequest,                                        //2
      observer: StreamObserver<HelloResponse>                       //3
  ) {
    with(observer) {
      val reply = HelloResponse.newBuilder()                        //2
                               .setMessage("Hello ${request.name}") //4
                               .build()
      onNext(reply)                                                 //5
      onCompleted()                                                 //5
    }
  }
}
  1. 检测grpc-server-spring-boot-starter注释并发挥其魔力。
  2. 上面项目中生成的引用类
  3. 方法签名允许一个StreamObserver参数。该类来自grpc-stub.jar
  4. 获取请求并为其添加前缀以构建响应消息。
  5. 播放事件。 

我们现在可以使用 启动 Web 应用程序./mvnw spring-boot:run

测试 gRPC 服务

帖子背后的整个想法是,使用常规工具访问 gRPC 服务是不可能的。尽管如此,我们仍然需要一个专用工具来进行测试。我找到了grpcurl。让我们安装它并使用它来列出可用的服务:

shell:

grpcurl --plaintext localhost:9090 list   #1-2
  1. 列出所有可用的 gRPC 服务,无需TLS 验证。 
  2. 为了避免 gRPC 和其他通道(例如 REST)之间的冲突,Spring Boot 使用了另一个端口。
ch.frankel.blog.grpc.model.HelloService   #1
grpc.health.v1.Health                     #2
grpc.reflection.v1alpha.ServerReflection  #2
  1. 我们定义的 gRPC 服务
  2. 自定义启动器提供的两个附加服务

我们还可以深入了解服务的结构:

Shell

grpcurl --plaintext localhost:9090 describe ch.frankel.blog.grpc.model.HelloService
Java

service HelloService {
  rpc SayHello ( .ch.frankel.blog.grpc.model.HelloRequest ) returns ( .ch.frankel.blog.grpc.model.HelloResponse );
}

最后,我们可以用数据调用服务:

Shell

grpcurl --plaintext -d '{"name": "John"}' localhost:9090 ch.frankel.blog.grpc.model.HelloService/SayHello
JSON

{
  "message": "Hello John"
}

使用常规工具访问 gRPC 服务

想象一下,我们有一个需要访问 gRPC 服务的常规 JavaScript 客户端应用程序。有什么选择?

一般的方法是通过grpc-web

用于浏览器客户端的 gRPC 的 JavaScript 实现。有关更多信息(包括快速入门),请参阅 gRPC-web 文档。

gRPC-web 客户端通过特殊代理连接到 gRPC 服务;默认情况下,gRPC-web 使用 Envoy。

在未来,我们希望 gRPC-web 能够在特定于语言的 Web 框架中得到支持,例如 Python、Java 和 Node。有关详细信息,请参阅路线图。- grpc-web

描述说明了一个限制:它仅适用于 JavaScript(截至目前)。然而,还有一个。这非常具有侵入性。您需要获取该proto文件、生成样板代码并让您的代码调用它。您必须为每种客户端类型执行此操作。更糟糕的是,如果 proto 文件发生变化,您需要在每个文件中重新生成客户端代码。

不过,如果您使用的是 API 网关,则还有另一种选择。我将描述如何使用Apache APISIX执行此操作,但也许其他网关也可以执行相同的操作。grpc-transcode是一个允许将 REST 调用转码到 gRPC 并再次转码的插件。

第一步是在 Apache APISIX 中注册 proto 文件:

Shell

curl http://localhost:9180/apisix/admin/protos/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d "{ \"content\": \"$(sed 's/"/\\"/g' ../model/src/main/proto/model.proto)\" }"

第二步是用上面的插件创建路由:

curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
  "uri": "/helloservice/sayhello",                           #1
  "plugins": {
    "grpc-transcode": {
      "proto_id": "1",                                       #2
      "service": "ch.frankel.blog.grpc.model.HelloService",  #3
      "method": "SayHello"                                   #4
    }
  },
  "upstream": {
    "scheme": "grpc",
    "nodes": {
      "server:9090": 1
    }
  }
}'
  1. 定义细化路由。 
  2. 引用上一个命令中定义的 proto 文件。 
  3. gRPC 服务
  4. gRPC 方法

此时,任何客户端都可以向定义的端点发出 HTTP 请求。Apache APISIX 将转码对 gRPC 的调用,将其转发到定义的服务,获取响应,然后再次转码。

curl localhost:9080/helloservice/sayhello?name=John
{"message":"Hello John"}

与 相比grpc-web,API 网关方法允许proto与单个组件共享文件:网关本身。

转码的好处

此时,我们可以利用 API 网关的功能。name一下,如果没有传递,我们想要一个默认值,例如World. 开发人员很乐意在代码中设置它,但对值的任何更改都需要完整的构建和部署。如果我们将默认值放入网关的路由处理链中,更改几乎可以立即发生。让我们相应地改变我们的路线:

curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
  "uri": "/helloservice/sayhello",
  "plugins": {
    "grpc-transcode": {
      ...
    },
    "serverless-pre-function": {                    #1
      "phase": "rewrite",                           #2
      "functions" : [
        "return function(conf, ctx)                 #3
          local core = require(\"apisix.core\")
          if not ngx.var.arg_name then
            local uri_args = core.request.get_uri_args(ctx)
            uri_args.name = \"World\"
            ngx.req.set_uri_args(uri_args)
          end
        end"
      ]
    }
  },
  "upstream": {
      ...
  }
}'
  1. 不适合时的通用通用插件
  2. 重写请求。
  3. 可以解决问题的 Magic Lua 代码

现在,我们可以使用空参数执行请求并获得预期结果:

curl localhost:9080/helloservice/sayhello?name

{“message”:”Hello World”}

结论

在本文中,我们简要描述了 gRPC 以及它如何有益于服务间通信。我们使用 Spring Boot 和grpc-server-spring-boot-starter. 不过,这是有代价的:普通客户无法访问该服务。我们不得不求助于grpcurl测试它。基于 JavaScript 的客户端或浏览器也是如此。

为了绕过这个限制,我们可以利用 API 网关。我演示了如何使用grpc-transcode插件配置 Apache APISIX 以获得预期的结果。

这篇文章的完整源代码可以在GitHub上找到。

原文链接:https://dzone.com/articles/grpc-on-the-client-side

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(1)

相关推荐

发表回复

登录后才能评论