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 将 Node.js 用于 Web 应用程序,通常结合服务器端 React 使用。考虑到这种语言混合、服务以及下文详述的未来数据管道工作,VSCO 确定 gRPC 和 Protocol Buffers 是实现进程间通信最实用的解决方案。从基于 HTTP/1.1 API 的 JSON 逐步迁移到基于 HTTP/2 的 gRPC 正在顺利进行。尽管如此,PHP 实现相对于其他语言的成熟度仍存在一些问题。

Protocol Buffers 在构建我们的数据生态系统方面尤其有价值,我们依靠它们以与语言无关的方式标准化并安全地演进我们的数据模式。举个例子,我们构建了一个 Go 服务,它从我们的 MySQL 和 MongoDB 数据库复制日志中获取数据,并将后端数据库更改转换为 Kafka 中不可变事件流,每个行或文档更改事件都编码为 protocol buffer。这个数据库事件流使我们能够根据需要添加实时数据消费者,而不会影响生产流量,也无需与其他系统协调。通过在传输到 Kafka 的过程中将所有数据库事件处理为 protocol buffers,我们可以确保数据以统一的方式编码,从而易于从多种语言中消费和使用。我们对 MySQL-binary-logMongo-oplog tailers 的实现已在 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,而我们转向单一的事件协议缓冲区定义,这揭示了客户端之间存在大量细微的错误和差异。

我们遇到的一个小障碍是,在加速推广过程中,我们的客户端需要保持与 JSON 实现的兼容性,并且需要与第三方 SDK 集成。这在 iOS 上只需要一些键值编码,但在 Android 上就变得更加困难。最终我们不得不编写一个 protobuf 编译器插件,以在保持足够性能的同时获得我们所需的反射功能。根据这次经验,我们创建了一个简洁的protoc 插件示例,该插件使用 Bazel 构建,并已在 GitHub 上提供。

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

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

到目前为止,我们对 gRPC 和 Protocol Buffers 的使用非常成功。它们并不能消除所有集成难题。然而,不难看出它们如何帮助我们的工程师避免编写大量样板 RPC 代码,同时绕开因宽松序列化格式带来的无休止的数据质量小问题。