翻译gRPC on the Client Side

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

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

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

此外,Protocol Buffers是一种二进制序列化机制,可以节省大量带宽。因此,gRPC是一种出色的跨系统通信选项。但如果所有组件都使用gRPC进行通信,那么简单的客户端如何调用它们呢?在本文中,我们将构建一个gRPC服务,并展示如何使用cURL调用它。

一个简单的gRPC服务gRPC的文档非常详尽,这里是一个摘要:

1.gRPC是一个远程过程调用框架。

2.它适用于广泛的编程语言。

3.它依赖于Protocol Buffers:

它是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.原型定义版本

2.包装

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 代码。

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

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

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

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

这是重要的一点:

@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.播放事件。

测试 gRPC 服务

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

grpcurl --plaintext localhost:9090 list   #1-2

1.列出所有可用的 gRPC 服务,无需 TLS 验证。

2.To 避免 gRPC 和其他通道(例如 REST)之间的冲突,Spring Boot 使用另一个端口。

ch.frankel.blog.grpc.model.HelloService   #1
grpc.health.v1.Health                     #2
grpc.reflection.v1alpha.ServerReflection  #2

1.我们定义的 gRPC 服务

2.定制启动器提供的两项附加服务

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

grpcurl --plaintext localhost:9090 describe ch.frankel.blog.grpc.model.HelloService
service HelloService {
  rpc SayHello ( .ch.frankel.blog.grpc.model.HelloRequest ) returns ( .ch.frankel.blog.grpc.model.HelloResponse );
}

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

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

使用常规工具访问 gRPC 服务

假设我们有一个需要访问 gRPC 服务的常规 JavaScript 客户端应用程序。有什么替代方案?

该描述指出了一个限制:它仅适用于JavaScript(截至目前)。但是,还有另一个。这是相当侵入性的。您需要获取原型文件,生成样板代码,并使您的代码调用它。您必须为每种客户端类型执行此操作。更糟糕的是,如果原型文件发生更改,则需要在每个文件中重新生成客户端代码。

但是,如果您使用的是 API 网关,则存在另一种选择。我将介绍如何使用 Apache APISIX 执行此操作,但也许其他网关也可以这样做。grpc-transcode 是一个插件,允许将 REST 调用转码为 gRPC 并再次返回。

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

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.引用上一个命令中定义的原型文件。

3.gRPC服务

4.gRPC方法

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

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

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

转码的好处

此时,我们可以利用 API 网关的功能。想象一下,如果没有传递名称,我们想要一个默认值,例如 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.魔术Lua代码可以解决问题

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

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

结论

在这篇文章中,我们简要介绍了 gRPC 以及它如何使服务间通信受益。我们使用 Spring Boot 和 grpc-server-spring-boot-starter 开发了一个简单的 gRPC 服务。但是,这是有代价的:普通客户端无法访问该服务。我们不得不求助于grpcurl来测试它。基于JavaScript或浏览器的客户端也是如此。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值