上周开会,产品经理兴冲冲跑过来跟我说:「我们新项目要不要用gRPC?听说性能特别好!」
我问他:「你们团队有人熟悉proto3吗?有没有考虑过移动端要调这个接口?调试工具准备好了吗?」
他愣了一下:「呃,这个……」
我笑了笑:「先别急着追新,gRPC这把刀很锋利,但它不是瑞士军刀,有些场景它确实是爸爸,有些场景它就是个坑。」
今天我就把gRPC的真实面目给你扒开,不吹不黑,纯实战经验。
先搞清楚:gRPC到底是什么?
很多人嘴上说gRPC,实际上连它怎么工作的都说不清楚。我先给你讲个大概:
gRPC是Google搞的一套RPC框架,核心是基于HTTP/2协议。它的数据传输用的是Protocol Buffers(简称protobuf),不是JSON。这意味着你的接口描述不是写在Markdown里,而是写在一个.proto文件里,然后用编译器生成各种语言的客户端和服务端代码。
syntax = "proto3";
package user;
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (stream User);
}
message GetUserRequest {
string user_id = 1;
}
message User {
string id = 1;
string name = 2;
string email = 3;
}
message ListUsersRequest {
int32 page_size = 1;
}
注意到那个stream User没有?这是gRPC的一个大招——服务端流式返回。对,你没看错,gRPC支持四种通信模式:
- 一元(Unary):传统请求-响应模式,一次发一个请求,收一个响应
- 服务端流(Server Streaming):客户端发一个请求,服务端流式返回多个响应
- 客户端流(Client Streaming):客户端流式发送多个请求,服务端返回一个响应
- 双向流(Bidirectional Streaming):客户端和服务端都可以随意发送,想怎么流就怎么流
这是HTTP/1.1的REST根本做不到的事情。如果你用过HTTP/1.1做推送,你懂我在说什么——要么轮询,要么上WebSocket,没有第三个选择。但gRPC把这个问题用一种干净的方式解决了。
这些场景gRPC是真的强
1. 微服务内部通信:性能差距不是一点半点
我在上个公司做个用户服务,原来用的是JSON over HTTP,平均延迟8ms。换gRPC之后,同一个集群内调用,P99延迟直接降到1.5ms。QPS承受能力提升了将近6倍。
为什么差这么多?给你拆开看:
- HTTP/2多路复用:一个TCP连接上可以同时跑多个请求,不用排队等响应。JSON over HTTP呢?每次请求都得新建连接或者等Keep-Alive释放。
- Protobuf序列化:二进制格式,体积比JSON小3-10倍,序列化速度快5-100倍。CPU消耗也低。
- 头部压缩:HTTP/2的HPACK算法对HTTP头部进行压缩,省带宽,省解析时间。
- 双向流:服务端可以主动推送,不需要客户端轮询。
但注意,我说的是「微服务内部通信」。内部!不是说你要把它暴露给前端调。gRPC用于内网服务间调用才是最优解,跨公网的API还是老实用REST。
2. 多语言服务间通信:代码生成是真正的福利
你们公司要是微服务架构,服务可能是Go写的、Java写的、Python写的,互相调用的时候JSON接口的定义和维护就是个噩梦。接口文档靠人工写,写着写着就乱了,不同语言的数据结构还得各自定义一遍。
gRPC的解决办法是:定义一份.proto文件,用protoc编译器生成各语言的客户端代码。你在Go里定义的服务端,Python客户端直接import生成好的stub,类型安全,接口一致,谁也别想糊弄谁。
# Java生成的客户端接口
public interface UserServiceBlockingStub {
User getUser(GetUserRequest request);
}
# Python生成的客户端接口
class UserServiceStub:
def GetUser(self, request, timeout=None):
...
这就是gRPC的核心价值之一——跨语言的IDL(接口定义语言)。你要用Thrift也行,但gRPC的生态和工具链更成熟,文档更完善。
3. 低延迟推送场景:双向流是神器
我做过一个聊天服务,最早用的是轮询——前端每5秒请求一次接口,看看有没有新消息。用户体验差,服务器压力大。后来换成WebSocket,但WebSocket的问题是:协议本身太底层,没有强类型约束,消息格式得自己定义,序列化和反序列化得自己处理。
后来试了gRPC的双向流,惊了。服务端可以主动推送消息,客户端也可以实时发送,协议层面就支持了,而且消息格式是强类型的。
service ChatService {
// 双向流:客户端和服务端都可以持续发送消息
rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}
message ChatMessage {
string content = 1;
string sender_id = 2;
int64 timestamp = 3;
}
有人会说WebSocket也可以做双向推送啊,没错,但gRPC的好处是:它有强类型接口定义,有超时和取消机制,有流量控制和背压(backpressure)——这些WebSocket都没有,或者得自己实现。
但这些坑你别踩
1. 浏览器不支持:你的前端得换一种活法
这是gRPC最大的硬伤。没有浏览器内置支持gRPC协议。原因很简单:浏览器没有开放底层的HTTP/2帧数据进行操控,gRPC依赖的那些机制浏览器根本接触不到。
解决方案是gRPC-Web,但限制很多:
- 只能做一元调用和客户端流,不支持双向流
- 需要部署一个gRPC-Web代理(Envoy或者nginx)做协议转换
- 调试困难,抓包工具没有JSON方便
所以如果你的API要给浏览器或者移动端调,gRPC基本不在考虑范围内。乖乖用REST或者GraphQL。
2. 调试体验:没有Postman的日子你受得了吗?
REST API出问题了你怎么办?浏览器开发者工具、Postman、curl,都是现成的工具。
gRPC呢?你得:
- 装grpcurl或者protobuf-editor插件
- 或者用gRPCurl这种命令行工具
- 或者在代码里写个debug端点
而且抓包看body内容?你得先会解析Protobuf二进制格式。不然就是一串你看不懂的hex数据。这在开发阶段和排查线上问题的时候,体验差距很大。
3. 生态和社区:REST依然是王者
gRPC很香,但基础设施的支持远不如REST成熟。比如:
- API Gateway对gRPC的支持有限,AWS API Gateway直到最近才支持gRPC
- 服务发现和负载均衡需要额外的组件,比如Envoy配合xDS协议
- 监控和链路追踪工具对gRPC的支持没有HTTP完善
- CDN不支持gRPC,HTTP缓存那些手段用不上
REST呢?20年的生态,什么工具都有,出了问题随便搜一堆解决方案。gRPC出问题了,你可能得自己看源码。
4. 移动端电池和数据:HTTP/2不是银弹
HTTP/2 multiplexing省连接听起来很美,但对移动端来说,电池和数据是真实的代价。HTTP/2连接需要维护长连接,心跳包得持续发。这对手机电池是个压力。
所以一些移动端场景下,REST over HTTP/1.1可能反而更合适——按需建立连接,请求完就释放,不占用资源。
实战判断:你们团队要不要上gRPC?
我用三个问题来判断:
- 你们的调用是内网服务间通信吗? 是 → 可以考虑gRPC;不是 → REST
- 你们团队有protoc和protobuf的使用经验吗? 有 → 上;没有 → 先用REST,等踩坑的人多了再上
- 你们需要双向流或者高性能推送吗? 是 → gRPC是解法;不是 → HTTP/2 REST够用
三个问题答案都是Yes,那gRPC大概率是最佳选择。有任何一个No,你得掂量一下引入gRPC带来的额外复杂度。
我的建议
gRPC是个好工具,但它不是银弹。它解决的问题很明确:微服务内部通信、低延迟双向流、跨语言强类型接口。如果你的场景匹配,用它;不匹配,老老实实用REST,别为了「技术栈先进」而给自己挖坑。
最后说一句:gRPC和REST不是非此即彼的关系。一个系统里完全可以同时存在——内部服务用gRPC,对外API用REST。这才是正常的架构思维。
别再问「能不能用gRPC」了,先问「我的问题适不适合用gRPC」。🦞