在如今云原生技术的大环境下,rpc 服务作为最重要的互联网技术,蓬勃发展,诞生了许多知名基于 rpc 协议的框架,其中就有本文的主角 gRPC 技术。

作者作为一名在 JD 实习的 Cpper,经过一段时间的学习和实践,发现了 C++ 与 Java 之间的种种不同,这也让我产生了一个想法:既然 rpc 需要做到的就是客户端无感知调用,那么客户端和服务端使用的语言也不应该成为约束,正巧在来 JD 实习之前,我就有接触过 gRPC,所以就想写一篇文章分析一下 gRPC 与当今主流 rpc 框架之间的区别与优势。

对比

1. gRPC 的实现原理

在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得使用者能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。

gRPC 的客户端和服务端可以用在多样化的环境中运行,使用者可以使用各种官方支持的语言来构建自己的应用。例如:你可以很轻易的使用 Java 作为 gRPC 的服务端,而在客户端使用 Ruby、Go、Python 等语言。

2. gRPC 的优势与劣势

2.1 优势:

2.1.1 多语言支持

gRPC 官方就支持多种编程语言,包括C#/.NET, C++, Dart, Go, Java, Kotlin, Node.js, Objective-C, PHP, Python, Ruby 等。开发人员无需考虑使用何种开发语言,可以充分利用语言的优势:C++ 的内存操作,go 语言的灵活,Java 的生态丰富......



2.1.2 基于 Protocol Buffers

gRPC 默认使用Protocol Buffers作为其接口定义语言(IDL)和底层消息交换格式。Protocol Buffers 是一种语言和平台中立的接口描述语言,允许开发者定义数据结构和服务接口,并且可以生成多种语言的代码。这使得在不同语言之间实现数据和服务接口的一致性变得简单。其消息格式采用二进制方式传输,比传统的 Json 体积更小。

具体的语法定义如下:

  1. 消息定义:在 .proto 文件中定义消息,消息由字段组成。字段有三种类型:required、optional、repeated,分别表示必须、可选和重复。
message Person {
  required string name = 1;
  optional int32 id = 2;
  repeated string email = 3;
}
  1. 枚举定义:枚举类型允许你定义一组有限的可能的值。
enum PhoneType {
  MOBILE = 0;
  HOME = 1;
  WORK = 2;
}
  1. 服务定义:服务允许你定义一组相互关联的 RPC(远程过程调用)。
service HelloService {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

4.字段编号:每个字段都有一个唯一的数字编号。这是必要的,因为在解析过程中,我们需要知道每个字段的顺序。在 .proto 文件中定义的每个字段都有默认值。例如,int32 类型的字段默认值为 0。

5.字段类型:每个字段都有一个类型。例如,string、int32、message 等。对于 message 类型的字段,你需要在括号内定义该消息的类型。对于 repeated 类型的字段,你可以将多个值放入一个列表中。例如,Person 消息中的 email 字段可以包含一个电子邮件地址列表。

6.服务调用:在客户端代码中,你可以使用生成的 stub 类来调用服务方法。例如,你可以这样调用 SayHello 方法:

HelloService.stub stub = HelloServiceGrpc.newBlockingStub(channel);
HelloReply response = stub.sayHello(HelloRequest.newBuilder().build());



2.1.3 跨平台兼容性

gRPC 支持多种软件和硬件平台。这种跨平台能力意味着 gRPC 不仅能在不同的操作系统上运行,还能在各种环境中有效运行,如服务器、移动设备Web 环境



2.1.4 底层调用协议

gRPC 使用 HTTP/2 作为底层传输协议克服了一些 HTTP/1.1 版本的一些限制。二进制组帧和压缩。 HTTP/2 协议在发送和接收方面均紧凑且高效。在单个 TCP 连接上多路复用多个 HTTP/2 调用。 多路复用可消除队头阻塞



2.1.5 强大的社区和生态系统

gRPC 的社区和生态系统提供了丰富的文档、教程和 API 参考,帮助开发者在不同的语言和平台上使用 gRPC。这种广泛的社区支持也促进了对新语言和平台的支持。例如:Dubbo3 对 gRPC 的支持gRPC-SwiftgRPC-Spring



github 上的 gRPC 生态支持



2.1.6 严格规范

具有 JSON 的 HTTP API 没有正式规范。 开发人员为 URL、HTTP 谓词和响应代码的最佳格式争论不休。gRPC 规范对 gRPC 服务必须遵循的格式进行了规定。 gRPC 消除了争论并为开发人员节省了时间,因为 gRPC 在各个平台和实现中都是一致的。



2.2 劣势:

2.2.1 浏览器支持有限

当下,不可能直接从浏览器调用 gRPC 服务。gRPC 大量使用 HTTP/2 功能,没有浏览器提供支持 gRPC 客户机的 Web 请求所需的控制级别。例如,浏览器不允许调用者要求使用的 HTTP/2,或者提供对底层 HTTP/2 框架的访问。



2.2.2 不是人类可读的

HTTP API 请求以文本形式发送,可以由人读取和创建。默认情况下,gRPC 消息使用 protobuf 编码。*虽然 protobuf 的发送和接收效率很高,但它的二进制格式是不可读的 *8。protobuf 需要在.proto 文件中指定的消息接口描述才能正确反序列化。需要额外的工具来分析线路上的 Protobuf 有效负载,并手工编写请求。

3. demo 展示

下面作者将使用 C++ 与 go 作为开发语言来展示 gRPC 强大的跨语言调用能力

项目结构:

grpc-demo
├── cpp
│   ├── CMakeLists.txt // C++的CMakeLists.txt文件,用来生成makefile
│   ├── cmake // 用来存放一些cmake函数
│   │   └── common.cmake // cmake函数
│   ├── include // 头文件
│   ├── proto // Protocol Buffers定义文件
│   │   └── helloworld.proto
│   └── src // C++源文件
│       └── main.cpp
├── go
│   ├── Makefile // makefile脚本
│   ├── go.mod // Go语言包管理
│   ├── proto
│   │   ├── helloworld.proto
│   ├── service
│   └── src // go源文件
│       └── main
│           └── main.go
└── proto
    └── helloworld.proto

项目源代码: https://github.com/ConstantineQAQ/grpc-demo

总结

回归题目,gRPC 因为他强大的可扩展性,轻便的底层传输格式,越来越多的企业在技术选型时选择了它,我也希望未来能有一款应用可以通过 gRPC 发挥出每种语言的优势,绽放出绚丽的色彩。


↙↙↙阅读原文可查看相关链接,并与作者交流