gRPC 基础:从零认识这个「高性能」家伙
先问一个问题:你平时调远程服务用什么?REST?JSON?
我以前也这么干。直到有一次,我负责的一个金融风控系统需要高频推送交易特征数据。REST 接口的延迟老是下不来,JSON 解析还特别占 CPU。嗯,那个项目让我彻底转向了 gRPC。
gRPC 是什么?简单说——它是一个高性能、跨语言的远程过程调用(RPC)框架。由 Google 开源,基于 HTTP/2 协议,默认用 Protocol Buffers(简称 Protobuf)做序列化。
说白了,你写一个本地的函数调用,gRPC 帮你把这个调用发送到另一台机器上执行,然后把结果返回给你。整个过程对开发者是透明的。
核心概念一览
理解 gRPC,必须先搞懂四个核心概念。我给你理一理:
| 概念 | 作用 | 类比(你更容易理解的) |
|---|---|---|
| Service | 定义一组远程方法的逻辑分组 | 像 Java 里的接口 |
| Method | Service 里的单个 RPC 调用 | 接口里的方法签名 |
| Message | 请求和响应的数据结构 | DTO 或结构体 |
| Stub | 客户端用于调用远程方法的代理对象 | 代理类,你只管调用 |
Service 与 Method
在 gRPC 中,你先用 .proto 文件定义 Service。举个例子:
service TokenService {
rpc CreateToken (CreateTokenRequest) returns (TokenResponse);
rpc ValidateToken (ValidateTokenRequest) returns (ValidationResult);
}
这个 TokenService 就是一个 Service,它包含两个 Method:CreateToken 和 ValidateToken。每个 Method 都有明确的输入和输出消息类型。
Message
Message 定义数据结构:
message CreateTokenRequest {
string user_id = 1;
int64 expiry_seconds = 2;
}
message TokenResponse {
string = 1;
int64 created_at = 2;
}
每个字段都有一个唯一的编号(1、2、3...),这个编号用于二进制编码,不能重复。你在后续升级时,不要随意修改已有字段的编号,否则会破坏兼容性。
reserved 关键字标记。结果新老版本客户端互相通信时,解析直接崩溃。嗯,后来我养成了一个习惯:只要字段不再用,先用 reserved 占住编号,再删除。
Stub
Stub 是客户端侧的代理。你编译 .proto 文件后,gRPC 会生成 Stub 类。你的业务代码只需要调用 Stub 上的方法,Stub 负责序列化、发送请求、接收响应、反序列化。
比如在 Java 中:
TokenServiceGrpc.TokenServiceBlockingStub stub =
TokenServiceGrpc.newBlockingStub(channel);
TokenResponse response = stub.createToken(
CreateTokenRequest.newBuilder()
.setUserId("u-123")
.setExpirySeconds(3600)
.build()
);
你只管 stub.createToken(...),背后所有的网络通信细节——都封装好了。
完整调用流程长啥样?
我画了一张图,把整个调用链路展示出来。你看看,从客户端代码到服务端代码,中间发生了什么:
从图上你能看到:客户端代码调用 Stub → Stub 序列化成 Protobuf → HTTP/2 传输 → 服务端 Stub 反序列化 → 业务代码处理 → 再原路返回。一层一层,分工明确。
和其他 RPC 框架的对比
你肯定会问:gRPC 跟 REST、Thrift 比,到底好在哪?我直接给你一张对比表,你一看就明白:
| 维度 | gRPC | REST | Thrift |
|---|---|---|---|
| 传输协议 | HTTP/2 | HTTP/1.1 | TCP / HTTP |
| 序列化格式 | Protobuf(二进制) | JSON / XML(文本) | Thrift Binary / Compact |
| 性能 | 高(低延迟、高吞吐) | 中(解析开销大) | 高(二进制编码) |
| 代码生成 | 全自动(从 .proto) | 手动或依赖 Swagger | 全自动(从 .thrift) |
| 流式通信 | 原生支持(4 种模式) | 仅 Server-Sent Events | 部分支持 |
| 跨语言 | 丰富(10+ 语言) | 任何语言皆可 | 丰富(与 gRPC 相当) |
| 学习曲线 | 中(需学 .proto) | 低(人人都会) | 中(需学 .thrift) |
| 浏览器支持 | 需 gRPC-Web 转译 | 原生 | 差 |
我的选择建议:
- 微服务内部通信 → 首选 gRPC。性能好,代码自动生成,省心。
- 对外公开 API、浏览器直接调用 → 选 REST。兼容性好,谁都能调。
- 已有 Thrift 技术栈 → 不需要硬迁。但新项目我建议看看 gRPC,生态更活跃。
gRPC 的优势,到底在哪儿?
我挑最影响你日常开发的三个点说:
- HTTP/2 多路复用:多个 RPC 调用共享同一个 TCP 连接,没有了 REST 下的连接开销。你想想看,100 个请求共用一个连接 vs 100 个连接,差距有多大。
- Protobuf 序列化性能碾压:二进制格式,体积小、解析快。我当初在风控系统里把 JSON 替换成 Protobuf 后,单个请求的 CPU 开销下降了 70%。
- 四种通信模式:一元(Unary)、服务端流、客户端流、双向流。同一套 .proto 定义,想怎么玩就怎么玩。
gRPC 的局限,你也该知道
这里有个坑:gRPC 默认的负载均衡是客户端侧实现的。你需要额外部署一个服务发现组件(比如 etcd、Consul),并且 Stub 启动时要配置好 resolved 地址。我第一次搭建时以为它跟 REST 一样靠 Nginx 反代就行了——结果发现完全不是一回事。
另外,浏览器没法直接发 gRPC 请求。你需要通过 gRPC-Web 代理转一层,或者改用 gRPC-Web 客户端库。这一点在做 Web 前端时要注意。
好,这就是 gRPC 的基础面貌。从核心概念到调用流程,再到选型对比,你应该有个整体手感了。后面我们会一步步深入,从 .proto 文件的编写开始,真正把 gRPC 跑起来。