Skip to content

入门: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 + StubChannel 是客户端到服务端 host:port 的底层连接(有 connected/idle 状态,可复用);Stub 是基于 Channel 生成的「本地代理」,提供与服务端同名的方法——调用 stub.xxx() 即发 RPC。
  • Deadline/Timeout:客户端设的最长等待时间,超时返回 DEADLINE_EXCEEDEDdeadline 会随调用链路传递(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 在这条路上做了三件关键事:

  1. 用 protobuf 当 IDL:开发者写一份 .proto 文件,描述「服务(Service)有哪些方法(Method)、每个方法的请求消息(Request)和响应消息(Response)长什么样」。这文件既是文档、又是契约、又是代码生成输入。
  2. 用 protobuf 当序列化格式:传输的数据不是 JSON 文本,而是 protobuf 二进制——更小更快,且强类型(编译期检查,不是运行时崩)。
  3. 跑在 HTTP/2 上:利用 HTTP/2 的多路复用与流式,一个 TCP 连接就能并发多个 RPC、还能做双向流式调用。
protobuf
// 一个典型的 .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 PushgRPC 不直接用,但 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(选型与现代化方案)。