2. gRPC 基础入门
本章是 gRPC 网关接入与微服务链路调优的基石。我们将从 gRPC 的定义出发,深入理解其核心序列化协议 Protocol Buffers,并通过与 RESTful API 的对比明确其适用场景,最后逐一剖析 gRPC 的四种通信模式。掌握这些基础概念,是后续配置网关、优化链路的前提。
2.1 gRPC 定义
gRPC 是一个由 Google 开发的高性能、开源、通用的远程过程调用(RPC)框架。它基于 HTTP/2 协议传输,使用 Protocol Buffers 作为接口定义语言(IDL)和序列化工具。gRPC 的核心思想是:定义一个服务,指定其可以被远程调用的方法及其参数和返回类型,服务端实现该接口并运行一个 gRPC 服务器来处理客户端调用,客户端则拥有一个存根(Stub),像调用本地对象一样直接调用远程服务。
核心特性:
- 基于 HTTP/2: 支持多路复用、双向流、头部压缩,显著降低延迟。
- Protocol Buffers: 高效的二进制序列化,体积小、速度快。
- 多语言支持: 自动生成多种语言的客户端和服务端代码。
- 双向流式通信: 支持四种通信模式,灵活应对复杂场景。
2.2 Protocol Buffers 协议
Protocol Buffers(简称 Protobuf)是 gRPC 的官方接口定义语言(IDL)与数据序列化协议。它使用 .proto 文件定义数据结构和服务接口,然后通过编译器(protoc)生成目标语言的代码。
2.2.1 定义消息与服务
以下是一个典型的 .proto 文件示例,定义了一个简单的用户查询服务:
syntax = "proto3";
package user.v1;
// 请求消息
message GetUserRequest {
int32 user_id = 1;
}
// 响应消息
message GetUserResponse {
int32 user_id = 1;
string name = 2;
string email = 3;
}
// 定义服务
service UserService {
rpc GetUser (GetUserRequest) returns (GetUserResponse);
}
2.2.2 Protobuf 与 JSON 序列化对比
| 对比维度 | Protocol Buffers | JSON |
|---|---|---|
| 数据格式 | 二进制 | 文本 |
| 序列化速度 | 极快 | 较慢 |
| 数据体积 | 小(压缩率高) | 大(包含冗余字段名) |
| 可读性 | 不可读(需工具解析) | 人类可读 |
| 模式定义 | 强制(.proto 文件) | 可选(弱类型) |
| 向后兼容 | 优秀(字段编号机制) | 需手动处理 |
最佳实践: 在微服务内部通信中,优先使用 Protobuf 以获得最佳性能;仅在需要对外暴露 API(如浏览器、移动端)时,才考虑通过 gRPC-Gateway 或 Envoy 转换为 JSON/HTTP。
2.3 gRPC 与 RESTful API 对比
RESTful API 长期以来是微服务通信的主流选择,但 gRPC 在特定场景下具有显著优势。下表从多个关键维度进行对比:
| 对比维度 | gRPC | RESTful API |
|---|---|---|
| 底层协议 | HTTP/2 | HTTP/1.1(通常) |
| 数据格式 | Protobuf(二进制) | JSON / XML(文本) |
| 接口定义 | 强类型(.proto 文件) | 松散(文档约定) |
| 通信模式 | Unary / Streaming / 双向流 | Unary(请求-响应) |
| 浏览器支持 | 需要 gRPC-Web 或代理 | 原生支持 |
| 代码生成 | 内置(protoc 自动生成) | 需借助 Swagger/OpenAPI 工具 |
| 性能(吞吐量) | 高 | 中 |
| 学习曲线 | 较陡 | 平缓 |
选型建议: 如果微服务之间需要高频、低延迟的内部调用,或者需要流式推送数据(如实时日志、监控指标),gRPC 是更优选择。如果系统需要广泛兼容浏览器、移动端或第三方系统,RESTful API 仍然是更通用的方案。在实际架构中,两者常通过网关(如 Envoy、gRPC-Gateway)共存。
2.4 gRPC 通信模式
gRPC 基于 HTTP/2 的流式特性,定义了四种通信模式。理解这些模式是设计微服务接口的关键。
2.4.1 四种模式详解
| 模式 | 描述 | 适用场景 |
|---|---|---|
| Unary(一元) | 客户端发送一个请求,服务端返回一个响应。最传统的 RPC 模式。 | 简单的 CRUD 操作、查询单个资源。 |
| Server Streaming(服务端流) | 客户端发送一个请求,服务端返回一个数据流(多个响应消息)。 | 订阅实时数据(如股票行情)、下载大文件、日志推送。 |
| Client Streaming(客户端流) | 客户端发送一个数据流(多个请求消息),服务端返回一个响应。 | 批量上传数据、IoT 设备上报、长时间运行的任务提交。 |
| Bidirectional Streaming(双向流) | 客户端和服务端可以同时发送和接收多个消息,双方独立读写。 | 实时聊天、在线游戏、交互式 AI 对话。 |
2.4.2 代码示例:Unary 模式
以下是一个简单的 Unary 模式 gRPC 调用示例(Go 语言):
// 客户端代码
conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
defer conn.Close()
client := pb.NewUserServiceClient(conn)
req := &pb.GetUserRequest{UserId: 123}
// 发起 Unary 调用
resp, err := client.GetUser(context.Background(), req)
if err != nil {
log.Fatalf("调用失败: %v", err)
}
log.Printf("用户信息: %+v", resp)
// 服务端代码
type userServer struct {
pb.UnimplementedUserServiceServer
}
func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
// 模拟数据库查询
return &pb.GetUserResponse{
UserId: req.UserId,
Name: "张三",
Email: "zhangsan@example.com",
}, nil
}
实践要点:
- 流式模式(Server/Client/Bidirectional)需要特别注意连接管理和背压(Backpressure)处理,避免内存溢出。
- 在微服务链路中,合理选择通信模式可以显著降低延迟。例如,日志收集服务使用 Client Streaming 比逐个发送 Unary 请求效率高得多。
- 双向流模式虽然灵活,但增加了编程复杂度,建议仅在确实需要实时双向交互时使用。