客户端的GRPC
#java #springboot #grpc #apacheapisix

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

XML具有架构验证的框外:XML文档可以声明必须符合的语法。基于XML的肥皂也受益于此。

其他序列化替代方案具有架构验证选项:,例如AvroKryoProtocol Buffers。有趣的是,GRPC使用Protobuf在分布式组件上提供RPC:

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

- Why gRPC?

此外,协议是二进制序列化机制,节省了很多带宽。因此,GRPC是系统间通信的绝佳选择。但是,如果您所有的组件都会与GRPC交谈,那么简单客户端如何称呼它们?在这篇文章中,我们将构建GRPC服务,并显示如何从卷发中调用它。

简单的GRPC服务

gRPC documentation详尽无遗,所以这是一个摘要:

  • GRPC是一个远程过程呼叫框架
  • 它在多种语言上起作用
  • 它依赖于协议缓冲区:

    协议缓冲区是Google的语言中立,平台中性的,可扩展的机制,用于序列化结构化数据 - 思考XML,但更小,更快,更简单。您定义了希望数据结构一次的方式,然后可以使用特殊生成的源代码来轻松地编写和从各种数据流和使用各种语言来编写和读取结构化数据。

    - 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. Protobuf定义版本
  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. proto文件生成Java代码

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

Proto model project structure

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

我们只需要特定的春季启动器依赖项即可将GRPC端点与弹簧启动集成:

<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. 玩活动

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

测试GRPC服务

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

grpcurl --plaintext localhost:9090 list   #1-2
  1. 列出所有可用的GRPC服务没有 tls验证
  2. 避免GRPC和其他频道之间发生冲突,例如 ,休息,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服务

想象一下,我们有一个常规的JavaScript客户端应用程序,需要访问GRPC服务。什么是替代方案?

一般方法是通过grpc-web

用于浏览器客户端的GRPC的JavaScript实现。有关更多信息,包括快速启动,请参阅GRPC-WEB文档。

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

将来,我们希望GRPC-WEB在特定于Python,Java和Node等语言的特定语言Web框架中得到支持。有关详细信息,请参阅路线图。

- grpc-web

描述指出一个限制:它仅适用于JavaScript(截至目前)。但是,还有另一个。这很干扰。您需要获取proto文件,生成样板代码,然后使您的代码调用。您必须对每种客户端类型进行操作。更糟糕的是,如果原始文件更改,则需要在其中每个文件中重新生成客户端代码。

但是,如果您使用的是API网关,则存在替代方案。我将描述如何使用Apache APISIX进行操作,但也许其他门户也可以做同样的事情。 grpc-transcode是一个插件,允许将REST呼叫转到GRPC并再次返回。

第一步是在Apache Apisix中注册原始文件:

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网关方法允许与单个组件共享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. 执行技巧的魔术LUA代码

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

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

结论

在这篇文章中,我们简要描述了GRPC及其如何使服务间沟通受益。我们使用Spring Boot和grpc-server-spring-boot-starter开发了简单的GRPC服务。不过,这是有代价的:普通客户无法访问该服务。我们不得不求助于grpcurl进行测试。基于JavaScript或浏览器的客户端也是如此。

要绕过此限制,我们可以利用API网关。我演示了如何使用grpc-transcode插件配置Apache Apisix以获得所需的结果。

可以在github上找到此帖子的完整源代码:

走得更远:

最初于3月16日在A Java Geek发表 th ,2023