2. gRPC 通信原理:Protocol Buffers 编码、HTTP/2 多路复用、gRPC 四种通信模式

gRPC 是 Google 开源的高性能远程过程调用框架,其底层依赖 Protocol Buffers 作为接口定义语言和序列化协议,HTTP/2 作为传输层协议。理解这三者的协同机制,是优化微服务间并发通信的前提。

2.1 Protocol Buffers 编码原理

Protocol Buffers(简称 protobuf)是一种语言中立、平台无关的二进制序列化格式。相比 JSON/XML,它在编码体积和解析速度上具有显著优势。

2.1.1 编码核心机制

  • Varint 编码:对整数使用可变长度编码,小数值占用更少字节(如 1 仅占 1 字节,300 占 2 字节)。
  • 字段编号 + 类型:每个字段使用 (field_number, wire_type) 键值对,接收方按编号匹配字段,支持向前/向后兼容。
  • 默认值省略:字段值为默认值时,编码时直接跳过,进一步压缩体积。
对比项 Protocol Buffers JSON XML
编码格式 二进制 文本 文本
体积(典型场景) JSON 的 30%~50% 基准 JSON 的 2~3 倍
解析速度 极快(无需字符串解析) 中等 慢
Schema 强制 是(.proto 文件) 否 可选(XSD)
向后兼容 原生支持 需手动处理 需手动处理
性能要点:在 Go 中,protobuf 序列化/反序列化不涉及反射(使用代码生成),单次操作耗时通常在纳秒级,远低于 JSON 的微秒级。

2.1.2 典型 .proto 定义示例

syntax = "proto3";

package order;

service OrderService {
  rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse);
}

message CreateOrderRequest {
  string user_id = 1;
  int64  amount = 2;
  repeated string item_ids = 3;
}

message CreateOrderResponse {
  string order_id = 1;
  int32  status_code = 2;
}

2.2 HTTP/2 多路复用

gRPC 强制使用 HTTP/2 作为传输层,这是其高性能的核心来源。HTTP/2 解决了 HTTP/1.x 的队头阻塞问题,并引入多路复用机制。

2.2.1 关键特性

  • 二进制分帧:HTTP/2 将请求/响应拆分为更小的帧(HEADERS、DATA、SETTINGS 等),以二进制格式传输。
  • 多路复用:在同一个 TCP 连接上,多个流(Stream)可以交错发送帧,互不阻塞。
  • 流优先级:客户端可以为不同流设置优先级,控制资源分配。
  • 头部压缩:使用 HPACK 算法压缩 HTTP 头部,减少冗余传输。
并发优化启示:HTTP/2 多路复用使得 gRPC 客户端可以在一个连接上并发发起多个 RPC 调用,无需像 HTTP/1.1 那样创建多个连接。这大幅降低了连接管理开销,是微服务间高并发通信的基础。
HTTP/1.1 队头阻塞 vs HTTP/2 多路复用 HTTP/1.1(串行) 请求 1(等待响应) 请求 2(阻塞中) 请求 3(阻塞中) HTTP/2(多路复用) 流 1:请求 1 帧 流 2:请求 2 帧(交错) 流 3:请求 3 帧(交错) 说明: HTTP/1.1 中,同一连接上的请求必须排队等待前一个响应返回(队头阻塞)。 HTTP/2 通过二进制分帧,将多个请求的帧交错传输,实现真正的并发复用。 gRPC 利用此特性,单个连接可承载数千个并发 RPC 流。

2.3 gRPC 四种通信模式

gRPC 基于 HTTP/2 流(Stream)抽象,定义了四种 RPC 通信模式,覆盖微服务间不同的交互场景。

2.3.1 模式对比

模式 客户端 服务端 典型场景 并发特性
Unary RPC 发送 1 个请求 返回 1 个响应 查询用户信息、下单 标准请求-响应,适合同步调用
Server Streaming RPC 发送 1 个请求 返回多个响应流 订阅事件、日志推送 服务端可并发推送,客户端单流接收
Client Streaming RPC 发送多个请求流 返回 1 个响应 批量上传、数据聚合 客户端可并发发送,服务端聚合后响应
Bidirectional Streaming RPC 发送多个请求流 返回多个响应流 实时聊天、协同编辑 双方独立并发读写,全双工通信

2.3.2 双向流模式详解

双向流模式是 gRPC 并发能力的集大成者。客户端和服务端各自维护一个独立的流,可以按任意顺序发送消息。在 Go 中,这通常通过 grpc.ServerStream 和 grpc.ClientStream 接口实现。

// proto 定义
service ChatService {
  rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}

// 服务端实现(Go)
func (s *chatServer) Chat(stream pb.ChatService_ChatServer) error {
  for {
    msg, err := stream.Recv()
    if err == io.EOF {
      return nil
    }
    if err != nil {
      return err
    }
    // 处理消息并并发回复
    go func(m *pb.ChatMessage) {
      stream.Send(&pb.ChatMessage{Content: "Echo: " + m.Content})
    }(msg)
  }
}
并发陷阱:在双向流中,stream.Send() 和 stream.Recv() 不是线程安全的。如果多个 goroutine 同时调用 Send,需要使用互斥锁或设计为单 goroutine 发送。gRPC 官方建议在流上只使用一个发送 goroutine 和一个接收 goroutine。
gRPC 四种通信模式架构 Unary RPC 客户端 服务端 请求 → 响应 Server Streaming 客户端 服务端 请求 → 流式响应 Client Streaming 客户端 服务端 流式请求 → 响应 Bidirectional 客户端 服务端 全双工流 模式选择建议: • Unary RPC:适用于大多数请求-响应场景,如查询、写入。 • Server Streaming:服务端需要持续推送数据时使用,如实时监控指标。 • Client Streaming:客户端需要批量发送数据时使用,如日志聚合。 • Bidirectional Streaming:需要全双工实时交互时使用,如聊天、协同。 • 在 Go 中,所有流模式都基于 HTTP/2 Stream,底层共享同一个 TCP 连接。

2.4 本章小结

  • Protocol Buffers 通过二进制编码、Varint 和字段编号机制,实现极致紧凑的序列化,是 gRPC 高性能的数据基础。
  • HTTP/2 多路复用 允许单个 TCP 连接承载数千个并发流,消除了队头阻塞,是 gRPC 高并发的传输保障。
  • 四种通信模式 覆盖了从简单请求-响应到全双工流的所有微服务交互场景,开发者应根据业务需求选择合适的模式。
  • 在 Go 中,gRPC 的并发模型与 goroutine 天然契合,但需要注意流操作的线程安全性。