VSCO 中的 gRPC
今天的客座文章来自 VSCO 的 Robert Sayre 和 Melinda Lu。
成立于 2011 年的 VSCO 是一个表达自我的社区,致力于赋能人们通过图像和文字进行创作、探索与连接。目前,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 作为进程间通信最实用的方案。从基于 HTTP/1.1 的 JSON API 到基于 HTTP/2 的 gRPC 的逐步迁移工作正在顺利进行中。话虽如此,PHP 实现相对于其他语言的成熟度确实存在一些问题。
Protocol Buffers 在构建我们的数据生态系统方面非常有价值,我们依赖它以语言无关的方式标准化我们的数据模式,并确保其安全演进。举例来说,我们构建了一个 Go 服务,该服务从我们的 MySQL 和 MongoDB 数据库复制日志中获取数据,并将后端数据库的变更转换为 Kafka 中的不可变事件流,其中每个行或文档变更事件都编码为 Protocol Buffer。这种数据库事件流使我们能够按需添加实时数据消费者,而不会影响生产流量,也无需与其他系统进行协调。通过将所有数据库事件在进入 Kafka 前处理为 Protocol Buffers,我们可以确保数据以统一的方式编码,从而轻松地在多种语言中使用。我们在 GitHub 上开源了我们的 MySQL-binary-log 和 Mongo-oplog 跟踪器。
在数据流水线的其他部分,我们已经开始使用 gRPC 和 Protocol Buffers 将行为事件从 iOS 和 Android 客户端传输到 Go 摄取服务,然后再将这些事件发布到 Kafka。为了支持这一高吞吐量用例,我们需要 (1) 一个高性能、容错且语言无关的 RPC 框架,(2) 一种在产品演进过程中确保数据兼容性的方法,以及 (3) 可水平扩展的基础设施。我们发现 gRPC、Protocol Buffers 和运行在 Kubernetes 上的 Go 服务非常契合这三点需求。由于这是我们第一个面向客户端的 Go gRPC 服务,我们确实遇到了一些新的摩擦点——特别是负载均衡支持以及像 curl 调试工具等便利设施,由于 HTTP/2 生态系统尚处于早期阶段,相对滞后。然而,使用 gRPC IDL 定义服务的简易性、拦截器等内置架构的使用,以及 Go 语言带来的扩展能力,使得这些权衡非常值得。
作为将 gRPC 引入移动客户端的第一步,我们已经在 iOS 和 Android 应用中发布了遥测代码。在 gRPC 1.0 版本中,这一过程相对直接。目前它们仅向我们的服务器发送事件,并没有对 gRPC 响应进行过多处理。之前的实现基于 JSON,而我们迁移到单一的 Protocol Buffer 事件定义后,发掘出了之前在客户端之间存在的许多细微 Bug 和差异。
我们遇到的一个小障碍是,随着部署的推进,客户端需要保持与现有 JSON 实现的兼容性,同时还要与供应商 SDK 集成。这在 iOS 上需要一些键值编码(key-value coding),但在 Android 上则更具挑战性。我们最终不得不编写一个 Protobuf 编译器插件,以便在保持足够性能的同时实现所需的反射特性。基于这一经验,我们已经在 GitHub 上提供了一个使用 Bazel 构建的简洁的 protoc 插件示例。
随着越来越多的数据以 Protocol Buffer 格式呈现,我们计划在这一统一模式的基础上,扩展我们的机器学习和分析系统。例如,我们将 Kafka 数据库复制流以 Apache Parquet(一种高效的列式磁盘存储格式)的形式写入 Amazon S3。Parquet 对 Protocol Buffers 有底层支持,因此我们可以利用现有的数据定义来编写优化的表,并按需进行部分反序列化。
从 S3 获取数据后,我们使用 Apache Spark 进行计算,它可以使用我们的 Protocol Buffer 定义来确定类型。我们还在利用 TensorFlow 构建新的机器学习应用。它原生使用 Protocol Buffers,并允许我们通过 TensorFlow Serving 将模型作为 gRPC 服务提供。
到目前为止,我们在 gRPC 和 Protocol Buffers 上的使用体验非常不错。它们虽然不能消除所有集成难题,但显而易见的是,它们帮助我们的工程师避免编写大量重复的 RPC 代码,同时规避了那些因松散序列化格式而导致的无休止的数据质量问题。