grpc 与 protobuf 的基本概念是什么?
一句话结论:protobuf 是二进制的结构化序列化协议,用 .proto 描述消息与接口并生成多语言代码(字段带编号,向后兼容靠“增字段不改号”);gRPC 是基于 HTTP/2 的 RPC 框架,支持一元、服务端流、客户端流、双向流四种调用,靠 protobuf 定义 service,适合微服务内部的高效通信。
基本概念分层
| 层 | 内容 |
|---|---|
| IDL | .proto:message 定义数据结构,service 定义 RPC 方法 |
| 序列化 | protobuf:字段编号加类型编码成紧凑二进制,编解码由生成代码完成 |
| 传输 | HTTP/2:多路复用、头部压缩、双向流、长连接 |
| 框架 | gRPC:stub/客户端、server、拦截器(interceptor)、deadline、metadata |
| 工具 | protoc 加插件生成代码;grpcurl/grpcui 调试;health/reflection 服务 |
四种调用模型
- 一元 Unary:一问一答,最常用。
- 服务端流:客户端发一个请求,服务端持续返回多条。
- 客户端流:客户端持续发送,服务端汇总后回一个响应。
- 双向流:双方同时持续收发(聊天、实时同步等场景)。
syntax = "proto3";
package user;
service UserService {
rpc GetUser (GetUserReq) returns (GetUserResp);
rpc WatchUsers (WatchReq) returns (stream UserEvent); // 服务端流
}
message GetUserReq { int64 id = 1; }
message GetUserResp { string name = 2; }
// 生成代码后,服务端实现接口并注册
type server struct {
pb.UnimplementedUserServiceServer
}
func (s *server) GetUser(ctx context.Context, req *pb.GetUserReq) (*pb.GetUserResp, error) {
return &pb.GetUserResp{Name: "alice"}, nil
}
func main() {
gs := grpc.NewServer()
pb.RegisterUserServiceServer(gs, &server{})
lis, _ := net.Listen("tcp", ":9000")
_ = gs.Serve(lis)
}
与 REST/JSON 的取舍
| 对比 | gRPC + protobuf | REST + JSON |
|---|---|---|
| 契约 | 强类型、先定义后实现 | 靠文档/OpenAPI |
| 效率 | 二进制小、HTTP/2 复用 | JSON 大、解析慢 |
| 流式 | 原生双向流 | 需另做 SSE/WebSocket |
| 生态 | 浏览器要 grpc-web 中转 | 全端通用 |
gRPC 也常用 context 传递 deadline 与 metadata(鉴权 token、trace id),配合拦截器统一加日志与错误码。
常见追问 / 记忆点
- 追问:protobuf 为什么能向后兼容?——字段靠编号不靠名字:新增用新号、删除留号占位,老客户端只读自己认识的字段。
- 追问:gRPC 为什么比 HTTP/1.1 加 JSON 快?——二进制编码加 HTTP/2 多路复用,省掉队头阻塞与反复握手开销。
- 记忆点:proto 定契约、protoc 生成代码、HTTP/2 传输;四种流模型;内部服务首选,对外慎选。