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: 高效的二进制序列化,体积小、速度快。
  • 多语言支持: 自动生成多种语言的客户端和服务端代码。
  • 双向流式通信: 支持四种通信模式,灵活应对复杂场景。
gRPC 核心架构图 gRPC 核心架构 gRPC 客户端 - 客户端 Stub - 调用本地方法 - 序列化 Protobuf - HTTP/2 请求 gRPC 服务端 - 服务实现 - 反序列化 Protobuf - 业务逻辑处理 - HTTP/2 响应 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 对话。
gRPC 四种通信模式 gRPC 四种通信模式 Unary 客户端 → 服务端 (单个请求) 服务端 → 客户端 (单个响应) Server Streaming 客户端 → 服务端 (单个请求) 服务端 → 客户端 (数据流) Client Streaming 客户端 → 服务端 (数据流) 服务端 → 客户端 (单个响应) Bidirectional 客户端 → 服务端 (数据流) 服务端 → 客户端 (数据流) 通信模式选择策略 Unary:简单请求-响应 | Server Streaming:服务端推送 Client Streaming:客户端批量提交 | Bidirectional:实时交互

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 请求效率高得多。
  • 双向流模式虽然灵活,但增加了编程复杂度,建议仅在确实需要实时双向交互时使用。