RSS

VSCO 的 gRPC 应用

今天的这篇客座文章来自 VSCO 的 Robert Sayre 和 Melinda Lu。

VSCO 成立于 2011 年,是一个表达社区,旨在赋能人们通过图像和文字进行创作、发现和连接。VSCO 正在将其技术栈迁移到 gRPC。

2015 年,用户增长迫使 VSCO 走向一条熟悉的道路。公司早期建立的 PHP 单体应用出现了性能问题,并且变得难以维护。我们尝试用 node.js、Go 和 Java 构建了一些较小的服务。与此同时,一个用于电子邮件、推送消息和应用内通知的更大规模的消息服务用 Go 构建。为了摆脱 JSON,我们选择了 Protocol Buffers 作为该系统的序列化格式。

如今,VSCO 大部分新服务都倾向于使用 Go 构建。但也存在例外,尤其是在现有成熟的 JVM 解决方案可用于特定问题的情况下。此外,VSCO 在 Web 应用中使用 node.js,通常采用服务器端 React。考虑到这种语言、服务组合,以及下面将详细介绍的一些未来的数据管道工作,VSCO 选择 gRPC 和 Protocol Buffers 作为进程间通信最实用的解决方案。从 JSON over HTTP/1.1 API 到 gRPC over HTTP/2 的逐步迁移正在顺利进行。尽管如此,PHP 实现的成熟度相对于其他语言还存在一些问题。

Protocol buffers 在构建我们的数据生态系统方面特别有价值,我们依靠它们以语言无关的方式标准化并安全地演进我们的数据 Schema。举例来说,我们构建了一个 Go 服务,它从 MySQL 和 MongoDB 数据库复制日志中读取数据,并将后端数据库变更转换为 Kafka 中的不可变事件流,每个行或文档变更事件都编码为 Protocol buffer。这个数据库事件流使我们能够根据需要添加实时数据消费者,而不会影响生产流量,也无需与其他系统协调。通过将所有数据库事件处理成 Protocol buffers 再发送到 Kafka,我们可以确保数据以统一的方式编码,从而便于多种语言进行消费和使用。我们实现的 MySQL-binary-logMongo-oplog tailer 已在 GitHub 上开源。

在我们的数据管道的其他地方,我们开始使用 gRPC 和 Protocol buffers 将 iOS 和 Android 客户端的行为事件发送到 Go 采集服务,该服务随后将这些事件发布到 Kafka。为了支持这个高并发用例,我们需要 (1) 一个高性能、容错、语言无关的 RPC 框架,(2) 一种在产品演进过程中确保数据兼容性的方法,以及 (3) 可水平扩展的基础设施。我们发现 gRPC、Protocol buffers 和在 Kubernetes 中运行的 Go 服务非常适合这三点。由于这是我们第一个面向客户端的 Go gRPC 服务,我们确实遇到了一些新的摩擦点——特别是由于 HTTP/2 生态系统尚处于早期阶段,负载均衡支持和类似 curl 的调试工具一直相对滞后。然而,使用 gRPC IDL 定义服务的便捷性、利用拦截器等内置架构以及使用 Go 进行扩展的便利性,使得权衡是值得的。

作为将 gRPC 引入移动客户端的第一步,我们在 iOS 和 Android 应用中发布了遥测代码。截至 gRPC 1.0 版,这个过程相对简单。到目前为止,它们只向我们的服务器发送事件,而不对 gRPC 响应做太多处理。之前的实现基于 JSON,而我们迁移到使用统一的 protocol buffer 定义事件后,发现了客户端之间存在的许多细微错误和差异。

我们遇到的一个小障碍是,在逐步推广过程中,我们的客户端需要与 JSON 实现保持兼容,并且需要与供应商 SDK 集成。这在 iOS 上只需要进行一些 key-value 编码,但在 Android 上则更加困难。我们最终不得不编写一个 protobuf 编译器插件,以在保持足够性能的同时获得所需的反射特性。借鉴这一经验,我们创建了一个使用 Bazel 构建的简洁 protoc 插件示例,并在 GitHub 上开源。

随着越来越多的数据以 protocol buffer 形式可用,我们计划基于这种统一的 Schema 扩展我们的机器学习和分析系统。例如,我们将 Kafka 数据库复制流写入 Amazon S3,格式为 Apache Parquet,这是一种高效的列式磁盘存储格式。Parquet 对 protocol buffers 有底层支持,因此我们可以使用现有的数据定义来编写优化过的表,并在需要时进行部分反序列化。

从 S3 中,我们使用 Apache Spark 对数据进行计算,Apache Spark 可以使用我们的 protocol buffer 定义来定义类型。我们还在使用 TensorFlow 构建新的机器学习应用。它原生支持 protocol buffers,并允许我们使用 TensorFlow Serving 将模型作为 gRPC 服务提供。

到目前为止,我们使用 gRPC 和 Protocol Buffers 的体验良好。它们并不能消除所有的集成难题。然而,很容易看到它们如何帮助我们的工程师避免编写大量重复的 RPC 代码,同时避开使用宽松序列化格式带来的无休止的数据质量问题。