原文链接🔗: https://kmcd.dev/posts/grpc-the-bad-parts/[1]
gRPC 作为一款高性能的 RPC 框架,在 Google 内部取得了巨大成功,并显著改变了我们部署 API 的方式。然而,它并非完美无缺。创建一个支持代码生成和多语言的 RPC 框架,必然会面临诸多挑战。笔者使用 gRPC 已近十年,在此有必要反思其有待改进之处。
首先,我们从一些细节入手。“一元请求”(Unary Request)是指客户端向服务器发送单个请求并接收单个响应的调用。为何 gRPC 必须使用如此晦涩的术语?每次使用都需解释,实在令人困扰。
说到一元请求,gRPC 的实现也显得过于复杂。尽管 gRPC 的流式接口功能强大,但对于简单的 RPC 调用,它引入了不必要的复杂性。这增加了调试 gRPC 调用的难度,因为即使是一元请求也需要分帧,这本是流式传输的特性。Protobuf 编码已足够复杂,无需在非必要之处增加额外的 gRPC 分帧。此外,对于 Web API 而言,使用 cURL 示例来解释 gRPC 的使用方式非常困难。“需要启用服务器反射吗?”这句话我已说过无数次。
这种复杂性也体现在工具层面,尤其是强制的代码生成步骤。对于重视运行时灵活性的动态语言而言,这可能是一个障碍。此外,一些开发者可能会对需要额外构建步骤的技术望而却步。现代 Web 开发已包含众多构建步骤,再增加一个往往令人犹豫。
对 HTTP/2 的依赖最初限制了 gRPC 的普及,因为并非所有平台和浏览器都完全支持它。尽管情况有所改善,但在某些环境下仍是挑战。即使支持 HTTP/2,浏览器也缺乏处理 HTTP Trailers 的能力,因此目前浏览器仍无法“原生”使用 gRPC。gRPC-Web 通过避免使用 Trailers 解决了这个问题,但通常需要额外的代理支持,这非常麻烦。
延迟采用 HTTP/3 协议可能阻碍了 gRPC 充分利用其性能和效率优势。笔者深受 HTTP/2 的“队头阻塞”[2](Head-of-line blocking)问题困扰,而 HTTP/3 本可彻底解决这一问题。一个大力推广 HTTP/2 的框架,却在 HTTP/3 支持上如此挣扎,着实令人费解。
早期缺乏标准化的 JSON
映射是另一个失误。这使得习惯于 JSON API 的开发者难以接受 gRPC
,我认为 gRPC
从未摆脱这种负面印象。Protobuf 类型与 JSON 之间的映射简化了与现有工具和系统的集成与互操作。当你说“是的,这是一种高效的二进制格式……但如果你想调试,可以设置这个标志来返回 JSON”时,Web 开发者会非常高兴。总之,现在 Protobuf 已有标准的 JSON 映射规则(以及反向),而 Protobuf 文本格式(Prototext)则显得多余。既然有了 JSON,文本格式便不再必要。不如将其废弃。如果大家同意,我愿意假装它从未存在过。
大多数 Protobuf 编码器/解码器都期望完全解析整个消息,然后将完整响应提供给消费者,但内存有限,有时我们需要处理更大的消息。有时,我们希望将这些大消息分段传输到其他地方,而不是将其全部保存在内存中。
因此,如果需要上传大型文件,我们需要实现某种文件分块机制。尽管分块是处理大文件的合理方案,但 gRPC 缺乏标准化的方法,可能导致实现不一致,增加开发工作量。
例如,以下是使用 gRPC 上传文件的示例:
syntax = "proto3";
package file_service;
service FileService {
rpc Upload(stream UploadRequest) returns(UploadResponse);
}
message UploadRequest {
string file_name = 1;
bytes chunk = 2;
}
message UploadResponse {
string etag = 1;
}
这既是 Protobuf 的优势,也是劣势。在 Protobuf 中定义这个概念非常容易,但实际实现却可能繁琐且容易出错。尽管 gRPC 的创建者 Google 已为其 API 找到了解决方案,但缺乏标准化的方法,让其他人需要重复造轮子。
你可能会想:“Google 在其大多数 API 中使用 gRPC,显然他们已经解决了这个问题。” 你是对的。他们确实有一个 gRPC 和 HTTP 版本用于下载(可能很大的)文件。我们可以直接比较 gRPC[3]和HTTP[4]版本,gRPC 版本明显更复杂。
我发现 gRPC/Protobuf 社区的活跃度不高。一些网站缺乏明显的活动,可能会让人觉得 gRPC 停滞不前或维护不力。这可能会阻碍潜在的采用者,并减缓社区的增长。这可能是因为选择太多,导致人们难以在 GitHub 问题之外找到愿意讨论 gRPC 的人。
很长一段时间里,每当看到一个代码库使用 Protobuf,我都会发现一个奇怪的脚本,用于下载随机的 Protobuf 文件,以高度自定义的方式放置它们,并执行一系列极其复杂的 protoc
调用。只有 Google 才会认为不解决依赖管理问题就是解决依赖管理问题。Google 有自己的依赖管理方式,我们只能望其项背。
尽管我对 gRPC 提出了批评,但我希望这些评论是建设性的。读到这里的人应该知道,许多问题已经或正在得到解决:
protoc
,并增加了 linting、破坏性变更检测、gRPC 版 cURL、与 Buf Schema Registry 的集成(真正的依赖管理!)等功能。此外,越来越多的工具开始支持 gRPC,例如 Postman、Insomnia 和 k6。尽管 gRPC 取得了不可否认的成功,但承认其不足之处至关重要,这有助于其持续发展和改进。通过解决学习曲线、兼容性、标准化和社区参与等方面的问题,我们可以释放 gRPC 的全部潜力,使其成为所有开发者都能轻松使用的工具
再谈 gRPC 的 Trailers 设计[8]
[1]https://kmcd.dev/posts/grpc-the-bad-parts/
[2]队头阻塞”: https://http3-explained.haxx.se/en/why-quic/why-tcphol
[3]gRPC: https://github.com/googleapis/google-cloud-go/blob/v0.114.0/storage/grpc_client.go#L996-L1152
[4]HTTP: https://github.com/googleapis/google-cloud-go/blob/v0.114.0/storage/http_client.go#L888-L911
[5]标准的JSON 映射: https://protobuf.dev/programming-guides/proto3/#json
[6]Buf Slack: https://buf.build/links/slack
[7]Buf CLI : https://buf.build/docs/ecosystem/cli-overview
[8]再谈 gRPC 的 Trailers 设计: https://taoshu.in/grpc-trailers.html