2. gRPC 基础回顾:Protocol Buffers 语法、gRPC 服务定义与四种通信模式

在构建 BFF(Backend For Frontend)聚合服务时,gRPC 凭借其高性能、强类型和原生流式通信能力,成为微服务间调用的首选协议。本章将系统回顾 gRPC 的核心基础:从接口定义语言(IDL)Protocol Buffers 的语法,到 gRPC 服务定义,再到四种 RPC 通信模式的适用场景与实现要点。

2.1 Protocol Buffers 语法核心

Protocol Buffers(简称 Protobuf)是 gRPC 的接口定义语言,负责描述数据结构和服务接口。其核心语法包括消息类型定义、字段编号、标量类型与枚举。

2.1.1 消息定义与字段规则

每个 Protobuf 消息由多个字段组成,每个字段必须指定唯一的编号(field number)。字段规则分为三种:

规则 关键字 说明 典型场景
单数 optional / 默认 字段最多出现一次,可省略 常规请求/响应字段
重复 repeated 字段可出现任意次(包括 0),顺序保留 列表、数组、批量数据
映射 map<K,V> 键值对集合,键类型必须为标量 字典、配置项、标签
最佳实践:字段编号 1~15 使用 1 字节编码,16~2047 使用 2 字节编码。高频字段应分配小编号以节省带宽。

2.1.2 标量类型与默认值

Protobuf 提供跨语言的标量类型映射,BFF 开发中常用的类型包括:

  • double / float:浮点数,对应 Java 的 double/float,Go 的 float64/float32
  • int32 / int64:可变长编码,适合大多数整型场景
  • uint32 / uint64:无符号整数
  • bool:布尔值
  • string:UTF-8 字符串
  • bytes:任意字节序列,适合二进制数据

每个字段在未设置时使用零值(0、空字符串、false),这与 JSON 的 null 语义不同,在 BFF 网关做协议转换时需特别注意。

2.1.3 枚举与嵌套消息

枚举类型用于定义有限的取值集合,必须从 0 开始。嵌套消息允许在消息内部定义子消息,适合构建层次化数据结构。

syntax = "proto3";

package bff.user.v1;

message Address {
  string street = 1;
  string city = 2;
  string zip_code = 3;
}

enum PhoneType {
  PHONE_TYPE_UNSPECIFIED = 0;
  PHONE_TYPE_MOBILE = 1;
  PHONE_TYPE_HOME = 2;
  PHONE_TYPE_WORK = 3;
}

message User {
  int64 user_id = 1;
  string name = 2;
  string email = 3;
  repeated Address addresses = 4;
  map<string, PhoneType> phones = 5;
}

2.2 gRPC 服务定义

gRPC 服务定义在 .proto 文件中使用 service 关键字声明,每个 RPC 方法指定输入消息类型和输出消息类型。服务定义是 BFF 网关配置的核心输入。

2.2.1 服务定义结构

service UserService {
  // 一元 RPC
  rpc GetUser(GetUserRequest) returns (GetUserResponse);

  // 服务端流式 RPC
  rpc ListUsers(ListUsersRequest) returns (stream User);

  // 客户端流式 RPC
  rpc BatchCreateUser(stream CreateUserRequest) returns (BatchCreateUserResponse);

  // 双向流式 RPC
  rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}

2.2.2 包名与导入

包名用于避免命名冲突,生成代码时对应目标语言的命名空间。跨文件引用使用 import 语句:

import "google/protobuf/timestamp.proto";
import "bff/common/types.proto";

2.3 四种 RPC 通信模式

gRPC 支持四种通信模式,BFF 聚合服务需要根据上游微服务接口特征选择合适的模式进行协议转换。

网关视角:BFF 网关通常将外部 HTTP/JSON 请求转换为 gRPC 调用。理解四种模式有助于正确配置请求/响应的流式处理逻辑。

2.3.1 一元 RPC(Unary RPC)

客户端发送单个请求,服务端返回单个响应。这是最常用的模式,适合查询、更新等同步操作。

  • 通信模型:请求 → 响应
  • 延迟特征:低延迟,等待完整响应
  • BFF 转换:直接映射为 HTTP POST + JSON 请求体/响应体
  • 典型场景:获取用户详情、提交订单

2.3.2 服务端流式 RPC(Server Streaming RPC)

客户端发送单个请求,服务端返回一个消息流。适合大数据量分页推送或实时事件通知。

  • 通信模型:请求 → 流式响应
  • 延迟特征:首字节延迟低,后续持续推送
  • BFF 转换:网关需将流式响应聚合为完整 JSON 数组,或使用 SSE/Chunked Transfer 转发
  • 典型场景:获取订单列表、日志流推送

2.3.3 客户端流式 RPC(Client Streaming RPC)

客户端发送一个消息流,服务端返回单个响应。适合批量上传或持续发送数据后汇总处理。

  • 通信模型:流式请求 → 响应
  • 延迟特征:客户端发送完成后服务端返回
  • BFF 转换:网关需缓冲或流式转发客户端请求,等待服务端响应后返回
  • 典型场景:批量创建用户、文件分片上传

2.3.4 双向流式 RPC(Bidirectional Streaming RPC)

客户端和服务端各自独立发送消息流,双方可同时读写。适合实时交互场景。

  • 通信模型:流式请求 ↔ 流式响应
  • 延迟特征:全双工,消息级低延迟
  • BFF 转换:网关需建立 WebSocket 或 SSE 连接,实现双向消息转发
  • 典型场景:实时聊天、协同编辑、游戏状态同步

2.4 四种模式对比与选择

模式 请求 响应 BFF 网关转换策略 适用场景
一元 单消息 单消息 HTTP POST ↔ gRPC Unary CRUD、查询
服务端流 单消息 消息流 HTTP SSE / 聚合为 JSON 数组 列表、推送
客户端流 消息流 单消息 HTTP Chunked Upload → 缓冲后调用 批量上传
双向流 消息流 消息流 WebSocket ↔ gRPC Bidirectional 实时交互

2.5 gRPC 通信模式架构图

下图展示了四种 RPC 模式在 BFF 网关与微服务之间的通信拓扑:

gRPC 四种通信模式架构 BFF 网关 微服务 ① 一元 RPC 请求 响应 ② 服务端流 请求 流式响应 ③ 客户端流 流式请求 响应 ④ 双向流 流式请求 流式响应 网关转换要点 一元 → HTTP POST;服务端流 → SSE/聚合数组;客户端流 → Chunked Upload;双向流 → WebSocket

2.6 Protobuf 与 gRPC 的 BFF 适配要点

在 BFF 网关配置中,理解 Protobuf 与 gRPC 的以下特性直接影响协议转换的正确性:

  • 字段编号不可变:一旦发布,字段编号不能修改,否则破坏向后兼容性。BFF 网关的映射配置需与 .proto 文件保持同步。
  • 未知字段保留:Protobuf 3 默认丢弃未知字段,BFF 网关若需透传额外数据,应使用 google.protobuf.Struct 或 Any 类型。
  • 流式 RPC 超时:流式连接需单独配置空闲超时和最大消息大小,避免网关资源泄漏。
  • 错误处理:gRPC 使用标准状态码(如 NOT_FOUND、INTERNAL),BFF 网关需将其映射为 HTTP 状态码和结构化错误体。
常见陷阱:Protobuf 的 int32 使用可变长编码,负数会占用更多字节。如果字段可能为负,应使用 sint32 或 sint64。BFF 网关在 JSON 转换时需确保数值范围不溢出。

2.7 本章小结

本章系统回顾了 gRPC 的基础知识:

  • Protocol Buffers 语法:消息定义、字段规则、标量类型与枚举
  • gRPC 服务定义:service 关键字、RPC 方法声明、包名与导入
  • 四种 RPC 模式:一元、服务端流、客户端流、双向流
  • BFF 网关视角下的模式选择与协议转换策略