VSCO 的 gRPC
我们今天的客座文章来自 VSCO 的 Robert Sayre 和 Melinda Lu。
VSCO 成立于 2011 年,VSCO 是一个表达社区,它使人们能够通过图像和文字进行创作、发现和连接。 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 的 JSON API 到基于 HTTP/2 的 gRPC 的逐步迁移正在进行中,并且进展顺利。尽管如此,与其他语言相比,PHP 实现的成熟度存在一些问题。
协议缓冲区在构建我们的数据生态系统方面尤其有价值,我们依靠它们来标准化和允许以语言无关的方式安全地演化我们的数据模式。例如,我们构建了一个 Go 服务,该服务从我们的 MySQL 和 MongoDB 数据库复制日志中获取数据,并将后端数据库更改转换为 Kafka 中不可变事件流,其中每个行或文档更改事件都编码为协议缓冲区。此数据库事件流使我们能够根据需要添加实时数据使用者,而不会影响生产流量,也不必与其他系统协调。通过将所有数据库事件处理为路由到 Kafka 的协议缓冲区,我们可以确保以统一的方式对数据进行编码,从而可以轻松地从多种语言中使用和使用数据。我们在 GitHub 上提供了 MySQL-binary-log 和 Mongo-oplog 尾部的实现。
在我们的数据管道的其他地方,我们已经开始使用 gRPC 和协议缓冲区将来自我们 iOS 和 Android 客户端的行为事件传递到 Go 提取服务,然后该服务将这些事件发布到 Kafka。为了支持这种高容量用例,我们需要(1)高性能、容错、语言无关的 RPC 框架,(2)一种确保数据兼容性的方法,随着我们产品的演进,以及(3)水平可扩展的基础设施。我们发现 gRPC、协议缓冲区和在 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 上提供。
随着越来越多的数据以协议缓冲区形式提供,我们计划在此统一模式的基础上扩展我们的机器学习和分析系统。例如,我们将 Kafka 数据库复制流以 Apache Parquet 格式写入 Amazon S3,这是一种高效的列式磁盘存储格式。Parquet 对协议缓冲区有底层支持,因此我们可以使用现有的数据定义来写入优化的表,并在需要时进行部分反序列化。
从 S3,我们使用 Apache Spark 对数据进行计算,它可以利用我们的协议缓冲区定义来定义类型。我们还在使用 TensorFlow 构建新的机器学习应用程序。它原生使用协议缓冲区,并允许我们使用 TensorFlow Serving 将我们的模型作为 gRPC 服务提供。
到目前为止,我们在 gRPC 和 Protocol Buffers 方面运气不错。它们并不能消除所有的集成难题。但是,很容易看出它们如何帮助我们的工程师避免编写大量样板 RPC 代码,同时避开使用较宽松的序列化格式带来的无休止的数据质量问题。