在移动端平台开发中,为了增加代码复用,降低开发成本,通常会需要采用跨平台的开发技术,花椒也不例外。本次新的单品开发,由于时间紧,人员有限,经过调研选型,最终确定了 flutter 方案(具体选型过程不在本文讨论之内)。
为了让客户端更专注业务实现,降低接口联调测试成本,我们选用了 gRPC 方案。gRPC是一个高性能、通用的开源 RPC 框架,由 Google 开发并基于 HTTP/2 协议标准而设计,基于 ProtoBuf(Protocol Buffers)序列化协议开发,且支持当前主流开发语言。gRPC通过定义一个服务并指定一个可以远程调用的带有参数和返回类型的的方法,使客户端可以直接调用不同机器上的服务应用的方法,就像是本地对象一样。在服务端,服务实现这个接口并且运行 gRPC 服务处理客户端调用。在客户端,有一个stub提供和服务端相同的方法。
特点
gRPC-Web 为前端浏览器提供了 Javascript 库用来访问 gRPC 服务,但是需要通过 Envoy 提供代理服务。相比 JSON 的方式对前端不够友好,同时也增加了服务端的部署成本。因此在这次项目中前端未使用 gRPC 服务,而是由 gRPC-Gateway 提供代理的 RESTful 接口。
grpc-gateway 是 protoc 的一个插件,它能读取 gRPC 的服务定义并生成反向代理服务器,将 RESTful 的 JSON 请求转换为 gRPC 的方式。这样无需太多工作即可实现一套基于 gRPC 服务的 RESTful 接口,方便前端使用调用接口,同时也方便开发过程中通过 Postman/Paw 之类的工具调试接口。
gateway -> gRPC 映射方式:
例如,gRPC 接口要求的通用的 metadata 参数(如 platform, device_id 等)在 HTTP RESTful 的传递方式如下:
dart
为了便于客户端调用,连接复用及通用参数传递,我们封装了 dart 的基础库。
BaseClient 维护了针对 HOST 缓存的连接池,同时也提供了接口需要传递的 metadata 信息。
golang
golang 后端服务需要同时支持 gRPC 和 gateway 两种请求方式。为了简化部署和上线依赖,gateway 和 gRPC 的功能放在了一起,并通过拦截器注入对应的功能,主要包括 gRPC 统计,访问日志,接口鉴权,请求参数校验,gateway JSON 编码等。
为了提高开发效率,方便维护及模块复用,服务端按功能进行组件化开发。每个组件可以单独运行一个服务,也可以和其它组件共同组成一个服务。每个组件都需要实现 Component 接口:
对应组件开发完成后,需要开发对应的服务容器,步骤如下。
1 base.Init(context.TODO(), cfg, &global.Callback{
2 Authenticator: &auth.Callback{},
3 LogCapture: &log.Capture{},
4 })
base.DefaultServer.AddPublicServer(rpcPort, gatewayPort, setting.TLSConfig)
1 base.DefaultServer.RegisterComponent(&user.Component{})
2 base.DefaultServer.RegisterComponent(&push.Component{})
3 ...
base.DefaultServer.Serve()
proto 规范
gRPC 基于标准化的 IDL(ProtoBuf)来生成服务器端和客户端代码,我们决定将所有的接口描述及文档说明都放到 proto 文件中,便于查看及修改。对 proto 的接口描述及注释的规范如下:
代码生成
golang
1 gengo:
2 @protoc -Iproto \\
3 -I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \\
4 -I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \\
5 -I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \\
6 --go_out=plugins=grpc:go/pb \\
7 --grpc-gateway_out=logtostderr=true:go/pb \\
8 --validate_out="lang=go:go/pb" \\
9 --auth_out="lang=go:go/pb" \\
10 proto/*.proto
golang 使用 go mod 的方式直接引入 pb 生成的 .go 文件
dart
修改 pubspec.yaml,执行 flutter packages get 或 flutter packages upgrade
1 dependencies:
2 flutter:
3 sdk: flutter
4
5 protobuf: ^0.13.4
6 grpc: ^1.0.1
7 user:
8 git:
9 url: git@github.com:project/repo.git
10 path: dart/user
gRPC gateway 提供了通过 proto 文件生成 swagger API 文档,缺点是只支持 gateway 的 RESTful 接口,并且默认的展示方式有点不符合我们的常规文档使用方式。
我们基于 protoc 插件开发了 protoc-gen-markdown 工具,可以由 proto 文件生成 markdown 文档,提供 gRPC 接口描述,以及 RESTful 接口描述及 JSON 示例,提供全文目录,支持锚点导航等。生成方式如下:
1gendoc:
2 @protoc -Iproto \\
3 -I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \\
4 -I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \\
5 -I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \\
6 --markdown_out=":doc" \\
7 proto/*.proto
文档会在对应路径生成接口列表 README.md,以及每个 protobuf 对应的接口文档。
传统的 RESTful 接口在调试及问题排查时,可以通过抓包或者 MitM(中间人攻击)的方式,配置也比较容易。而 gRPC 因为使用了 HTTP2 及 protobuf 二进制流,抓包及数据流反解难度相对较高,调试及问题排查时会比较复杂。为了解决这个问题,我们通过服务端注入的方式,配合查询后台过滤对应的请求日志,从而实现如下类似抓包的效果。
Go语言中文网,致力于每日分享编码、开源等知识,欢迎关注我,会有意想不到的收获!
本文由花椒服务端团队原创授权发布