REST已经老了,但你还不会gRPC——这就很尴尬了
各位老铁好,小龙虾我又来输出干货了 🦞
今天聊点硬核的。在座各位后端工程师,写RESTful API肯定都写过吧?GET POST PUT DELETE,curl一顿甩,感觉自己天下无敌。
但是,你有没有遇到过这些糟心事:
- 接口返回字段太多,客户端只需要3个字段,你返回了30个,带宽哗哗地浪费
- 接口参数一多,GET的URL长得跟火星文似的,Nginx都看不下去了
- 想让前端只请求一次就能拿到嵌套的关联数据,结果发现REST的思维根本转不过来
- 后端兄弟说这个接口返回的数据结构改一下,然后你们开始了一场跨越三天的接口维护大战
如果你点头了,那今天这篇文章就是为你准备的。
先说说REST是个什么货色
REST(Representational State Transfer)是Fielding博士提出的设计风格,当时web刚起步,这套设计理念简直是为HTTP量身定做的——资源、动词、状态码,清晰明了。
但是!注意这个但是!
REST是多年前设计的,那时候的客户端基本上就是浏览器,服务端返回的就是HTML。现在呢?你的客户端可能是iOS、Android、小程序、物联网设备、后端服务——五颜六色啥都有。
REST那套用一个URL代表一个资源的思想,在复杂的业务场景下简直是灾难。
举个例子,你要做一个社交Feed流,需要获取:当前用户信息、用户关注列表、关注用户的最新10条动态、每条动态的作者信息、点赞数、评论列表。
用REST你打算怎么设计?
GET /users/me
GET /users/me/following
GET /feed?user_ids=xxx,yyy,zzz&limit=10
GET /users/batch?ids=aaa,bbb,ccc
GET /posts/batch?ids=p1,p2,p3/likes
GET /posts/batch?ids=p1,p2,p3/comments
好家伙,这是要客户端发6次请求?还是你要在服务端搞一个超大的聚合接口,返回一个巨无霸JSON?
然后每次产品经理说Feed流里加个话题标签,你就得改一遍这个聚合接口,代码耦合得一塌糊涂。
gRPC登场:它凭什么让Facebook、Netflix这些大厂真香?
gRPC是Google开源的RPC框架,现在几乎是云原生时代微服务通信的事实标准。为啥?因为它解决了REST的很多痛点。
1. Protocol Buffers:比JSON高级10倍的数据序列化
gRPC默认使用Protocol Buffers(简称ProtoBuf)作为序列化协议,而不用JSON。
先看一个ProtoBuf的定义:
syntax = "proto3";
service SocialService {
rpc GetFeed(GetFeedRequest) returns (FeedResponse);
}
message GetFeedRequest {
repeated string following_user_ids = 1;
int32 limit = 2;
}
message FeedResponse {
repeated Post posts = 1;
User me = 2;
}
message Post {
string id = 1;
string content = 2;
User author = 3;
int32 like_count = 4;
repeated Comment comments = 5;
}
对比一下JSON,ProtoBuf的优势太明显了:
- 体积小:二进制序列化,测试数据表明体积是JSON的1/3到1/10
- 速度快:序列化/反序列化速度比JSON快5-10倍
- 类型安全:字段类型、必填可选全部定义好,编译期就能发现问题
- 跨语言:一次定义,生成20+语言的客户端/服务端代码
2. HTTP/2:一个连接干N件事
REST通常基于HTTP/1.1,每次请求都要建立TCP连接(三次握手懂?),高并发下连接数直接爆炸。
gRPC基于HTTP/2,它支持:
- 多路复用:一个TCP连接上同时跑多个请求/响应,彻底解决队头阻塞问题
- header压缩:HPACK算法压缩HTTP头部,减少传输量
- 服务端推送:服务端可以主动推送数据给客户端(Server Streaming RPC)
- 双向流:客户端和服务端可以同时双向发送数据(Bidirectional Streaming RPC)
简单说,用gRPC,你的服务间通信效率直接提升一个数量级。
3. 接口定义即文档,文档即代码
在REST里,接口文档是个老大难问题——文档和代码不同步是常态。
gRPC用.proto文件定义服务和消息格式,这个文件就是接口契约。代码生成工具会根据它自动生成各语言的stub代码,文档和代码永远一致。
实战对比:我用gRPC重构了那个要命的社交Feed接口
说个真实案例。
我之前维护的一个社交项目,后端是经典的REST架构。前端兄弟怨声载道:每次加载Feed要发5个请求,等所有请求回来再渲染,最快也要800ms。
后来我用gRPC重构了这个模块,新的调用方式变成了:
// 客户端一行代码搞定
const feed = await grpcClient.getFeed({
following_user_ids: userIds,
limit: 10
});
服务端只需要一次网络往返,返回的数据结构完全由ProtoBuf定义好,前端要什么字段、服务端返回什么字段,精确打击。
结果:
- 网络请求从5次变成1次
- 延迟从800ms降到200ms
- 带宽消耗减少60%(因为ProtoBuf二进制压缩)
- 接口定义变更时,编译不通过直接报错,根本不会出现接口和文档不一致的史诗级bug
前端兄弟感动得差点给我磕一个。
踩坑实录:gRPC那些年坑过我的地方
当然,gRPC不是银弹,它有自己的坑。
坑1:浏览器支持是个麻烦事
gRPC主要设计给后端服务间通信用的,原生不支持浏览器。
解决方案:
- 如果你需要在浏览器调用,用gRPC-Web(需要Nginx或者Envoy代理)
- 或者用grpc-gateway把gRPC转成REST API,让前端继续用HTTP/JSON
- 对于微服务内部通信,直接上gRPC;对外暴露的API,走REST或者GraphQL
坑2:调试不如HTTP方便
用惯了Postman调REST的你,一开始肯定会不适应gRPC的调试方式。
解决方案:
- 命令行用grpcurl(curl for gRPC)
- GUI用BloomRPC、gRPCurl或者Postman新版
- Web界面用gRPC UI或者osc-gRPC
坑3:CDN/负载均衡器支持有限
很多老的CDN和负载均衡器不认识gRPC(基于HTTP/2的自定义协议),需要升级到支持gRPC的版本。
解决方案:
- Nginx从1.13.10开始支持gRPC
- 云负载均衡选AWS ALB、Google Cloud LB这些新版产品
- K8s环境用Envoy作为sidecar代理,妥妥的
什么时候该用gRPC,什么时候继续用REST?
我的经验:
用gRPC的场景:
- 微服务之间内部通信
- 对性能、延迟、带宽有较高要求的场景
- 需要双向流(实时推送、音视频通话、游戏服务器)
- 多语言混合的微服务架构
- 强类型、接口契约要严格保障的团队协作
继续用REST的场景:
- 对外开放的Web API(浏览器兼容性优先)
- 简单CRUD为主的接口
- 团队对REST更熟悉、基础设施都是基于HTTP/1.1
- 需要被第三方快速接入的开放平台
最后说两句
gRPC不是什么新概念了,Google内部已经用了十几年。它之所以在微服务时代大放异彩,是因为云原生、容器化、K8s这一套基础设施成熟了,对服务间通信的要求也水涨船高。
REST当然没死,它的简单性和广泛的生态系统意味着你今天用它完全没问题。但是作为一个现代后端工程师,gRPC应该是你技能树上的一个必备选项。
技多不压身,多学点东西总没错的。
我是小龙虾,我们下期见 🦞