入门:gRPC、protobuf 与 HTTP/2
基于 gRPC 1.71 / protobuf proto3 · 核于 2026-08
速查
- gRPC 定义:Google 开源的高性能 RPC 框架,跑在 HTTP/2 上,用 protobuf 作 IDL + 序列化。开发者写
.proto定义服务/方法/消息,工具跨 11+ 种语言生成客户端 stub 和服务端骨架——调用远程方法像调本地函数。 - protobuf(Protocol Buffers):Google 的结构化数据序列化协议,是 gRPC 的 IDL(接口定义语言)+ 消息编码格式。语言中立、平台中立、二进制紧凑(比 JSON 小 20%~70%、解析快 5~10 倍)。当前主版本 proto3(语法精简、去 required、默认值规则明确)。
- HTTP/2 是 gRPC 的传输底座:①多路复用(一个 TCP 连接并发多个请求,无队头阻塞);②二进制分帧(数据切成 frame);③头部压缩(HPACK);④双向流(server push / 双向流式 RPC 的物理基础)。这就是 gRPC 能做流式调用的根因。
- 四种服务方法:①Unary(一元):一请求一响应,类似普通函数调用,最常用;②Server Streaming(服务端流):一请求 → 服务端回一个流(订阅/推送/分页);③Client Streaming(客户端流):客户端发一个流 → 服务端回一个响应(批量上传/攒批);④Bidirectional Streaming(双向流):两边都开流,独立读写(聊天/实时双向交互)。
- Channel + Stub:Channel 是客户端到服务端 host:port 的底层连接(有 connected/idle 状态,可复用);Stub 是基于 Channel 生成的「本地代理」,提供与服务端同名的方法——调用 stub.xxx() 即发 RPC。
- Deadline/Timeout:客户端设的最长等待时间,超时返回
DEADLINE_EXCEEDED。deadline 会随调用链路传递(A→B→C 都能查到剩余时间),是分布式超时的关键。 - Status Code:gRPC 用统一的状态码表示结果——
OK(0)、NOT_FOUND(5)、PERMISSION_DENIED(7)、RESOURCE_EXHAUSTED(8)、UNAVAILABLE(14)、DEADLINE_EXCEEDED(4)、UNAUTHENTICATED(16)等共 17 个,比 HTTP 的几个状态码语义精细。 - Metadata(元数据):key-value 形式的「请求头」,用于传认证 token、trace 上下文(如
traceparent)、自定义数据。键规则:ASCII 键小写、不能以grpc-开头(保留前缀);二进制值键以-bin结尾。 - 跨语言互通:11+ 种语言(Go/Java/Python/Node/C++/Rust/C#/Ruby/PHP/Android/Swift)由官方生成器支持,同一份 proto 即可互通——这是 gRPC 在多语言微服务团队里的杀手锏。
- 进阶顺序:协议与服务方法详解 → 与 REST/GraphQL 对比与 Connect → 参考。
一、gRPC 是什么:用 protobuf + HTTP/2 做 RPC
RPC(Remote Procedure Call,远程过程调用)的目标是让调用远程机器上的方法,像调用本地函数一样。gRPC 在这条路上做了三件关键事:
- 用 protobuf 当 IDL:开发者写一份
.proto文件,描述「服务(Service)有哪些方法(Method)、每个方法的请求消息(Request)和响应消息(Response)长什么样」。这文件既是文档、又是契约、又是代码生成输入。 - 用 protobuf 当序列化格式:传输的数据不是 JSON 文本,而是 protobuf 二进制——更小更快,且强类型(编译期检查,不是运行时崩)。
- 跑在 HTTP/2 上:利用 HTTP/2 的多路复用与流式,一个 TCP 连接就能并发多个 RPC、还能做双向流式调用。
// 一个典型的 .proto —— 定义服务与消息
syntax = "proto3";
package demo.v1;
option go_package = "demo/v1;demo";
// 服务定义:远程可调用的方法集合
service Greeter {
rpc SayHello(HelloRequest) returns (HelloReply); // Unary
rpc Subscribe(HelloRequest) returns (stream HelloReply); // Server Streaming
rpc Upload(stream HelloRequest) returns (HelloReply); // Client Streaming
rpc Chat(stream HelloRequest) returns (stream HelloReply);// Bidirectional
}
message HelloRequest { string name = 1; } // 字段号 1,类型 string
message HelloReply { string message = 1; }写好这份 proto,用 protoc + 语言插件生成 Go/Java/Node 代码:服务端实现 Greeter 接口、客户端拿到 GreeterClient(Stub)——客户端调 stub.SayHello({name:"x"}),背后 gRPC 把请求序列化、走 HTTP/2 发给服务端、服务端反序列化调实现、把响应传回。跨语言零成本:Go 客户端可以直连 Java 服务端。
二、protobuf:紧凑且强类型的序列化
protobuf 的核心思想是「用字段号(field number)而非字段名编码」。一条消息在二进制里就是一连串 tag-length-value:tag 里存字段号 + wire type(变长整数、定长 64 位等),value 紧跟。字段名「name」「message」根本不进二进制——这就是它比 JSON 小的根因(JSON 每个键都存完整字符串)。
| 序列化格式 | 大小(同一条数据) | 解析速度 | 人类可读 | 类型安全 |
|---|---|---|---|---|
| JSON | 大(键名占空间) | 慢(字符串解析) | 是 | 弱(运行时校验) |
| protobuf(proto3) | 小 20%~70% | 快 5~10 倍 | 否 | 强(编译期) |
- proto3 的关键变化:①去掉
required(全optional/repeated,默认值规则简化);②标量字段有默认值(string 默认""、int 默认0)且默认值不参与编码(接收方分不出「显式传了默认值」还是「没传」——这是 proto3 的已知坑,proto3 14+ 加了optional关键字可显式区分);③map类型原生支持。 - 字段号不可复用:字段号(
= 1的那个 1)一旦发布就不能改语义——删字段要reserved保留号,避免旧客户端传来的数据被新代码误读成新字段。 - 向后兼容规则:加字段(旧客户端不传新字段,新代码用默认值)、改字段名(按号解析,名字无所谓)、
string↔bytes/int32↔uint32等同 wire type 可互转。改字段类型到不同 wire type、复用已删字段号都是破坏性变更。
三、HTTP/2:gRPC 的性能底座
gRPC 不是「在 HTTP/1.1 上跑 JSON」,而是深度利用 HTTP/2 的能力。理解 HTTP/2 是理解 gRPC 性能与流式调用的前提:
| HTTP/2 特性 | gRPC 怎么用 |
|---|---|
| 多路复用(Multiplexing) | 一个 TCP 连接并发 N 个 RPC,互不阻塞——REST 要并发 100 请求得开 100 连接,gRPC 一条够 |
| 二进制分帧(Binary Framing) | 请求/响应切成 frame,HTTP/2 stream 上传——流式 RPC 的物理基础 |
| 双向流(Stream) | 一个 stream 内双向发 frame → 服务端流/双向流 RPC 直接映射 |
| 头部压缩(HPACK) | metadata(认证头)压缩,重复请求几乎不传重复头 |
| Server Push | gRPC 不直接用,但 stream 模型受其启发 |
关键:gRPC 用一个 HTTP/2 stream 承载一个 RPC,stream 内的消息(protobuf 编码的 message)切成多个 DATA frame 传输。多路复用让多个 RPC 共享一个 TCP 连接——这就是 gRPC 高并发低延迟的根本。
四、四种服务方法
gRPC 支持四种服务方法,覆盖几乎所有通信模式。消息在同一 RPC 内的顺序保证(先发的先到):
| 方法 | 请求 | 响应 | 典型场景 |
|---|---|---|---|
| Unary(一元) | 单个 | 单个 | 普通 API(查用户、下单),最常用 |
| Server Streaming | 单个 | 流(多条) | 订阅推送、分页拉取、行情推送、日志尾随 |
| Client Streaming | 流(多条) | 单个 | 批量上传、攒批写入、传感器数据汇聚 |
| Bidirectional | 流 | 流 | 聊天、协同编辑、实时游戏、双向心跳 |
- Unary 最简单:调用
stub.SayHello(req)返回Promise<HelloReply>,和普通异步函数一样。90% 的业务用这个。 - Server Streaming:客户端发一次,服务端
while(...) yield msg,客户端for await (const msg of call) {...}逐条收。适合「问一次,服务端慢慢推」。 - Client Streaming:客户端
call.write(msg)多次,最后call.end(),服务端收完所有返回一个响应。适合攒批。 - Bidirectional:两边都拿到一个 stream 对象,独立 read/write——服务端不必等客户端说完才说话。聊天室、实时协作的经典选择。
五、Channel、Stub 与生命周期
- Channel:客户端到
host:port的逻辑连接。一个 Channel 内部维护 HTTP/2 连接池,所有 stub 共享一个 Channel 即可高并发(多路复用)。Channel 有状态(idle/connecting/ready/transient_failure/shutdown),可配置 keepalive、最大重试、负载均衡策略(round_robin/pick_first)。 - Stub(Client):基于 Channel 生成的类型化客户端。
const stub = new GreeterClient(channel); stub.SayHello(req)——stub 的方法签名由 proto 决定,类型安全。 - 一次 Unary RPC 的旅程:客户端调 stub → 生成请求 protobuf → 经 client interceptor → 序列化进 HTTP/2 DATA frame → 网络传到服务端 → server interceptor → 反序列化 → 业务 handler → 返回响应 protobuf → 反向链路 → 客户端拿到结果。
- Deadline:客户端必须设置(生产环境不设 deadline 是 gRPC 反模式),deadline 一路传递——下游服务能查到「还剩多少时间」,避免雪崩。
下一步
理解了 gRPC 的协议基础后,下一步深入协议与服务方法详解(protobuf 编码细节、HTTP/2 帧结构、四种方法调用流程、metadata 与拦截器),以及与 REST/GraphQL 对比与 Connect(选型与现代化方案)。