大多数使用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 代码。
编译后,结构应如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/20f4726471ce14208285fdc99b5f9928.png)
我们可以将类打包在 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或浏览器的客户端也是如此。