从REST到gRPC,一个API选型的思考框架

团队做微服务,服务之间怎么通信,是个绕不开的问题。

REST+ JSON已经用了很多年,简单、成熟,调试也方便。gRPC也很流行,性能、类型安全、代码生成,这些优势确实很吸引人。

所以,很多团队都会遇到一个实际问题:现有的REST接口,有没有必要换成gRPC?

什么时候值得换,换了之后又会带来什么问题?我们这篇内容来聊一下这个问题。

REST:不是协议,是一种风格

REST不是协议,而是Roy Fielding在2000年博士论文里提出的一种架构风格。实际开发里大家说REST API,通常就是HTTP + JSON:把服务端的数据当成资源,用URL标识资源,再通过 GET、POST、PUT、DELETE 这些HTTP方法来操作。

REST能用这么多年,有几个原因。HTTP到处都是,基本不需要额外的基础设施。curl、Postman,甚至浏览器都能直接发请求。新同事接手接口,也不用先学一套新的通信协议。

另外,REST的请求通常是无状态的,服务端不用保存客户端的会话状态,加机器就能做水平扩展。HTTP本身还有缓存机制,配合CDN、反向代理,读请求也比较容易做缓存。

还有一点对客户端很重要:JSON对字段变化比较宽容。服务端新增一个字段,老客户端通常可以直接忽略,不影响原来的逻辑。移动端尤其需要这一点,毕竟不可能要求所有用户同时升级App。

当然,REST用久了,问题也会慢慢出现。

第一个是返回的数据不好控制。

同一个用户接口,列表页可能只需要头像和昵称,详情页却要完整的用户资料。如果为了不同页面做很多套接口,维护起来麻烦;如果统一返回完整数据,列表页又会拿到一堆用不到的字段。反过来,如果返回的数据不够,一个页面还得再调几个接口。

第二个是接口版本越来越难清理。

字段名、数据结构或者业务逻辑发生较大变化时,常见做法是增加/v1/users/v2/users这样的版本。新版本上线容易,老版本什么时候能删却很难判断。时间一长,代码里就会留下不少已经没人敢动的旧接口。

第三个是HTTP/1.1的队头阻塞。

服务间调用如果使用HTTP/1.1,同一个TCP连接上的请求会受到前面请求的影响(HTTP/1.1虽支持Pipeline,但因中间件兼容性问题实际几乎不用)。客户端通常会通过连接池建立多个连接来提高并发,但连接数也会随之增加。

这些问题在服务间调用很多的时候会更加明显。一个外部请求背后,可能还会串起好几个内部服务调用。调用次数一多,网络传输、序列化和连接带来的开销也就不能忽略了。

gRPC:先定契约,再写代码

gRPC是Google在2015年开源的RPC框架。它和REST不太一样,不是围绕资源设计,而是围绕服务和方法来设计。

在.proto文件里,把服务有哪些方法、每个方法接收什么参数、返回什么结果定义清楚,然后通过工具自动生成客户端和服务端代码。

// 用户服务的接口定义
service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc ListOrders(ListOrdersRequest) returns (stream Order);
}

请求和响应的具体字段用message定义,这里就不展开了。简单来说,proto文件就是客户端和服务端共同遵守的一份接口定义。字段名、类型都以它为准,再由它生成对应代码。

这样做的好处是:proto文件通过字段编号(tag)和类型定义了强契约。任何破坏兼容性的变更(如复用已删除字段的tag、修改字段类型)都能在生成代码时直接报错拦住,避免了REST中常见的隐式契约导致的联调事故。需要注意的是,protobuf本身设计为向后兼容,新增字段或删除字段(保留tag)不会导致老客户端编译报错,真正的安全保障来自于团队配套的版本管理规范。多人、多团队协作时,这套机制能显著降低沟通成本和线上故障风险。

gRPC主要解决三件事。

二进制序列化。 gRPC默认使用Protocol Buffers(protobuf)序列化数据,传输的是二进制,而不是JSON文本。同样一份数据,protobuf通常比JSON小很多,尤其是ID、时间戳、枚举值这类数字字段。

这是因为protobuf对数字采用了更紧凑的编码方式,小整数甚至只需要1个字节。数据更小,网络传输更快,带宽占用也更低。

HTTP/2多路复用。 一个TCP连接上可以同时处理多个请求和响应,一个请求慢了,不会把其他请求一起堵住。

所以,客户端不需要为了并发请求维护大量连接,一个连接就可以处理很多请求;服务端需要维护的连接数也会少很多。
在这里插入图片描述

除了常规一问一答,gRPC还支持服务端流、客户端流、双向流三种模式。proto文件语言中立,一份定义可以生成Java、Go、Python、C++多语言的代码,多语言团队之间调用不需要手写序列化逻辑。

好处讲完,再说说不方便的地方。

浏览器:原生gRPC依赖HTTP/2的底层特性(如Trailer和二进制帧),而浏览器的网络API并未开放这些能力,导致前端无法像调REST那样直接调用原生gRPC。虽然可以通过gRPC-Web或Connect Protocol等方案来兼容,但这通常需要在网关层(如Envoy)加一层代理做协议转换,并且对双向流等高级特性的支持依然受限。接口一多,这层额外的代理不仅增加了部署节点,也拉高了整体的维护成本。

调试:REST接口出了问题,直接用curl请求一下,返回的JSON基本一眼就能看懂。gRPC传输的是二进制数据,通常要借助工具调试。线上抓包时,看到的也是一堆二进制数据,不像JSON那么直观。现在一些工具已经可以把gRPC数据转换成可读格式,但整体体验还是不如REST方便

负载均衡:gRPC使用HTTP/2长连接,连接建立后可以复用很长时间。这样一来,传统按照请求分发的负载均衡方式就没那么简单了。尤其在Kubernetes这类环境里,Pod扩缩容或重启时,长连接不会自动断开重连,容易导致请求持续打到已下线或负载过高的旧实例上。虽然在Kubernetes环境中可以通过Service Mesh(如Istio)来解决,但这也引入了额外的基础设施。

什么时候用REST,什么时候用gRPC

先看你的API是给谁用的。

对外API,选REST。开放给第三方开发者、合作伙伴或前端页面直接调用时,JSON可读、可调试、浏览器原生支持,第三方接入成本低。你不需要假设调用方会去装protoc编译器或理解proto文件。

内部微服务之间,考虑gRPC。服务间调用量大、对延迟敏感、双方都是内部的,二进制序列化、多路复用、编译期类型检查这些优势能发挥出来。蛮多国内外大厂内部微服务通信大量使用gRPC,看中的就是这些。用REST也不是不行,等调用规模大到序列化开销不可忽视时再评估也不迟。

需要流式传输,选gRPC。聊天消息推送、实时数据同步、大文件分片上传、AI模型流式输出,这些场景gRPC的流式接口直接就能用。REST要做这些,通常得引入WebSocket或SSE,架构上多一层东西。

跨语言互操作,gRPC更省心。Go写的服务调Java写的服务,Java又调Python,proto文件作为中立的契约,各语言团队生成自己的代码就能互相调用。

常见的做法是混合架构:对外暴露REST接口,网关层把外部请求转成gRPC调用,内部服务之间走gRPC。外部调用方不需要感知内部的gRPC细节。
在这里插入图片描述

选型时看四件事

  1. API的主要消费者是谁?外部开发者还是内部服务?
  2. 对延迟和带宽的敏感度有多高?
  3. 是否需要流式传输或实时推送?
  4. 团队有没有能力维护gRPC的工具链(proto管理、代码生成流水线、调试工具)?

没有哪个问题能一票否决,要看自己的业务特点。

小结

聊到这儿,还有一个问题想说:gRPC性能好这么多,为什么团队换起来还是犹豫?

换技术栈的成本不只是学习成本。proto文件要统一管理,代码生成要接入流水线,调试工具要重新配,负载均衡要重新设计。这些事对一个小团队来说,是实打实的时间投入。

如果系统还没有性能问题,不用为了gRPC的性能优势提前增加复杂度。初期用REST快速交付,等服务间调用量上来之后,再把高频调用逐步切到gRPC。切换是渐进的,不需要一次到位,也没必要全量切换。

不要为了想象中的性能,先把系统搞复杂了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SamDeepThinking

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值