[翻译]Twirp: a sweet new RPC framework for Go

前言

Twirp 是由 Twitch 公司开源的一个 RPC 框架,从 2018 年开源至今一直在国内默默无闻,我认为一个很重要的原因是国内的后端开发仍然停留在 RESTful API 的开发思维里,并没有多少开发者真正转型到 RPC 的开发生态里。并且因为鼎鼎大名的 Google 开源了 gRPC,进一步导致其他 RPC 框架无人问津。本文致力于翻译并精简 twich 公司的一篇博文来介绍这个好用且简单的 RPC 框架。

为什么要使用 RPC

在 2015 年,Twitch 公司做出了巨大的努力去将一个巨无霸 app 拆分成多个独立的服务,在进行微服务拆分的时候,需要考虑很多 API 设计的问题,包括:

  • 如何接收请求,譬如现在要开发一个更新用户 email 地址的接口,那么以下的 RESTful 格式 url 看起来都很合理:

    • POST /users/:id/email
    • PUT /users/:id/email
    • POST(or PUT) /users/:username
    • PATCH /users/:username/info
  • 是否要设置一些特殊的 headers

  • url 的版本号如何体现

上面还只是在创建 API 的阶段需要解决问题,在长期运营 API 的时候还需要处理如下问题:

  • 不同开发人员 / 不同开发阶段使用的 url 格式不一致,这样会导致指标采集很难统一处理请求日志,并且很难维护 API 的接口文档
  • 服务端代码在版本迭代之后,你需要手写去更新客户端的代码

Twirp 通过代码自动生成的方法来解决上述的问题,用户只需要编写一个简洁的 protobuf 文件来描述 API,Twirp 会生成一个go 文件,主要包含了 HTTP 的处理代码(包括路由,序列化等),举例如下:

syntax = “proto3”;
package Twitch.users.email;

service EmailBoss {
rpc UpdateEmail(UpdateEmailRequest) returns (UpdateEmailResponse);
}

message UpdateEmailRequest {
int64 user_id = 1;
string new_email = 2;
}

message UpdateEmailResponse {
// EmailBoss may clean up the email it receives to trim whitespace
// or remove superfluous characters. The cleaned_email will be the
// email actually stored after this cleaning.
string cleaned_email = 1;
}

Twirp 生成的 go 文件主要包括以下内容

type EmailBoss interface {
UpdateEmail(context.Context, *UpdateEmailRequest) (*UpdateEmailResponse, error)
}

// 将实现了 EmailBoss 接口的类都作为 http.Handler 暴露出去
func NewEmailBossServer(svc EmailBoss) http.Handler

// 同样提供了一个 client 的构造器来和 server 通信
func NewEmailBossProtobufClient(hostport string, client *http.Client) EmailBoss

有了这个自动生成的 go 文件,后端开发人员就不需要再考虑 url schema 等问题,只需要专注于实现接口的处理逻辑。而 client 也只需要指定 server 的 ip 和端口,就可以直接使用生成的实例来调用 server 的服务。

Twirp 生成的所有请求都是基于 Http1.1,并且只能使用 POST 请求提交参数,payload 既可以是 JSON,也可以是二进制编译的 protobuf。因此,对于上面的例子来说:

  • url 为:/Twirp/Twitch.users.email.EmailBoss/UpdateEmail
  • http 请求方法为 POST,并且只能为 POST
  • 请求的 payload 如果使用 json 格式,header 需要指定为:Content-Type: application/json,如果使用 protobuf,指定为:application/protobuf
  • 如果请求出现问题,server 返回的 response 同样用json 编码,会包含出错信息和标准的错误码

为什么不使用 gRPC

Google 提供的 gRPC 框架同样也会自动生成代码,Twitch 公司一开始也是使用 gRPC,但遇到了四个核心的问题是 gRPC 无法解决的,从而创建了 Twirp 框架:

  • 不支持 HTTP1.1,gRPC 只支持 http/2,因为它依赖http/2 来提供全双工流,但 Twirp 可以同时支持 http1.1 和 http/2。因为很多负载均衡(软件和硬件,譬如 AWS
    的 ELB)现阶段都只支持 http1.1。Twirp 最大的缺点就是不支持流式 RPC,但 Twitch 发现绝大部分的请求都不需要使用流式 rpc
  • gRPC 生成的 go 代码比较少,因此调用了一个大型的运行时库:grpc-go,不幸的是,这个库偶尔会出现重大更新,就会导致无法编译旧的客户端代码,从而需要大规模更新客户端代码才能保证 gRPC 版本的一致性。而 Twirp 生成的代码里几乎包含了所有需要用到的代码,并且尽量少地进行重大版本更新,以此来保证旧系统的平稳运行。
  • grpc-go 并没有使用标准的网络库,而是自己实现了一个完整的 http/2 ,以此引入了自定义的网络通信控制机制。但这样的做法也会引入一些其他人难以理解的代码,并引发bug。
  • gRPC 只支持二进制格式的 protobuf payload,而 Twirp 还另外支持 json 格式的 payload,这样的好处有:1、易于编写跨语言的客户端代码,毕竟 Restful API 也是使用 json 格式的 payload。2、很容易使用 cURL 工具来调试服务端代码。而 gRPC 使用的二进制 protobuf payload 使用起来会感觉完全不透明

总的来说,gRPC 最大的优势就是支持双向流式 RPC,客户端和服务端之间可以不间断地发送数据流,而 Twirp 没有这样的功能,只支持一问一答的交互方式,在 Twitch 公司里并没有一定要使用 gRPC 双向流式 RPC 的场景。并且 grpc-go 还集成了一篮子的插件,从负载均衡服务发现,新的思想意味新的学习成本。

Twirp 在生产环境的表现

总结上文,如果你在编写后台服务,使用结构化的 rpc 要比自己手动编写 Restful API 更好,Twitch 已经使用 Twirp 来编写大量的后台系统。Twirp 的简洁性尤其值得称赞,因为你只需要专注于后端的代码逻辑,而不用纠结这个请求应该使用 PUT / POST 还是 DELETE 请求。简洁意味着稳定,Twitch 尚未观测到任何一例因为 Twirp 的 bug 带来的业务中断,因为 Twirp 本身没有太多可能引发故障的情况。

Reference

[1] Twirp: a sweet new RPC framework for Go

文章作者: kylinlin
文章链接: https://kylinlingh.github.io/2023/09/08/%E7%BF%BB%E8%AF%91-Twirp-a-sweet-new-RPC-framework-for-Go/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Water&Melon's Blog