作者创建的图像。
这篇文章最初发布于 https://vutr.substack.com。
上下文
需求
逻辑构建块
深入探讨Uber的开源解决方案:Apache Kafka, Apache Flink, Apache Pinot, HDFS, Presto
用例
Uber 是一家科技公司,它在2010年代初通过推出一款应用程序,使得司机和乘客之间能够轻松连接,从而彻底改变了出租车市场。截至2023年,有1.37亿人在每月使用Uber或Uber Eats一次。此外,在2023年,Uber司机完成了94.4亿次行程。为了支持业务,Uber积极利用数据分析和机器学习模型进行运营。从动态定价的Uber乘车服务到UberEats餐厅管理仪表板,所有这些都需要实时数据高效运行。在这篇博客文章中,让我们一起看看Uber如何管理其支持众多实时应用程序的幕后基础设施。
注意:这篇博客是我阅读论文《Uber的实时数据基础设施》(Real-time Data Infrastructure at Uber)后的笔记。
优步基础设施中的高级数据流。由作者创建,参考文献为 参考。
优步的业务具有高度的实时性。数据不断从多个来源收集:司机、乘客、餐厅、食客或后端服务。优步处理这些数据以提取有价值的信息,为诸如客户激励、欺诈检测和机器学习模型预测等许多用例做出实时决策。实时数据处理在优步的业务中起着至关重要的作用。该公司依赖开源解决方案并进行内部改进来构建实时基础设施。
从高层次来看,Uber中的实时数据处理主要包括三个大领域:
每个区域都有三个基本的扩展挑战:
Uber 的实时基础设施需要以下几点:
在本节中,我们将看一下Uber基础设施的主要逻辑构建模块:
作者创建的图像。
下面的部分将介绍优步采用的开源系统,对应于相应的构建模块。
流存储
作者创建的图像。
Apache Kafka 是一个流行的开源事件流处理系统,在业界被广泛采用。它最初由 LinkedIn 开发,并于 2011 年初开源。除了性能之外,Kafka 被采用的其他因素还包括简单性、生态系统成熟度和开源社区。
在 Uber,他们拥有最大的 Apache Kafka 部署之一:每天处理数万亿条消息和数 PB 的数据。Kafka 在 Uber 中应用于多种工作流程:从乘客和司机应用程序传播事件数据,支持流处理分析平台,或将数据库变更日志传递给下游订阅者。由于 Uber 的独特规模特性,他们对 Kafka 进行了以下增强:
逻辑集群
作者创建的图片。
Uber 开发了一个联邦 Kafka 集群设置,该设置隐藏了集群细节,使生产者和消费者无需了解这些细节。
失败消息的队列
作者创建的图像。
在某些情况下,下游系统无法处理消息(例如,消息损坏)。最初,有以下两种处理这种情况的选项:
然而,Uber 有许多场景需要既不能丢失数据也不能阻塞处理。为了解决这类用例,Uber 在 Kafka 之上构建了 Dead Letter Queues (DLQ) 策略:如果消费者在重试后仍无法处理某条消息,它将把该消息发布到 DLQ。这样,未处理的消息将被单独处理,不会影响其他消息。
中间层
作者创建的图像。
在拥有数以万计运行 Kafka 的应用程序的情况下,Uber 面临着调试这些应用程序和升级客户端库的挑战。用户还在组织内部使用多种编程语言与 Kafka 进行交互,这使得提供多语言支持变得困难。
Uber 构建了一个消费者代理层来解决这些问题;代理从 Kafka 读取消息并将其路由到 gRPC 服务端点。它处理了消费者库的复杂性,应用程序只需采用一个薄的 gRPC 客户端。当下游服务无法接收或处理某些消息时,代理可以重试路由,并在多次重试失败后将消息发送到死信队列(DLQ)。代理还将 Kafka 中的消息分发机制从消息轮询更改为基于推送的消息分发。这提高了消费吞吐量,并允许更多的并发应用程序处理机会。
高效地在集群之间复制主题
由于业务规模庞大,Uber 在不同的数据中心使用多个 Kafka 集群。采用这种部署方式,Uber 需要 Kafka 的跨集群数据复制,原因有二:
Uber 开发并开源了一个名为 uReplicator 的可靠解决方案,用于 Kafka 的复制目的。该复制器具有一个重新平衡算法,在重新平衡期间将受影响的主题分区数量保持在尽可能低的水平。此外,在流量激增的情况下,它可以将负载重新分配给备用工作进程。我稍微研究了一下 uReplicator 的高级架构,以下是我的发现:
作者创建的图片,参考:链接。
Uber 还开发并开源了一个名为 Chaperone 的服务,以确保跨集群复制过程中不会丢失数据。它收集关键统计信息,例如每个复制阶段的独特消息数量。然后,Chaperone 对比这些统计信息,并在出现不匹配时生成警报。
流处理
作者创建的图像。
Uber 使用 Apache Flink 构建了处理来自 Kafka 的所有实时数据的流处理平台。Flink 提供了一个具有高吞吐量和低延迟的分布式流处理框架。Uber 选择 Apache Flink 的原因包括:
Uber 对 Apache Flink 做了以下贡献和改进:
使用SQL构建流式分析应用程序。
作者创建的图像。
Uber 开发了一个名为 Flink SQL 的层,它可以在 Flink 上运行。它可以将 Apache Calcite 的 SQL 输入 转换为 Flink 作业。处理器将查询编译成一个分布式 Flink 应用程序,并管理其整个生命周期,让用户专注于处理逻辑。在后台,系统将 SQL 输入转换为逻辑计划,然后经过优化器形成物理计划。最后,该计划通过 Flink API 转换为 Flink 作业。
然而,隐藏复杂性给用户带来了对基础设施团队管理生产任务的操作负担。Uber 需要应对这些挑战:
注意:Flink SQL 是一个具有无界输入和输出的流处理引擎。其语义与批处理 SQL 系统(如 Presto)不同,将在后面进行讨论。
作者创建的图片。
Uber 的 Flink 统一平台导致了分层架构的出现,从而提高了可扩展性和可扩展性。
感谢这些改进,Flink 已经成为 Uber 的中央处理平台,负责数千个作业。现在,让我们继续介绍用于 OLAP 构建块的下一个开源系统:Apache Pinot。
OLAP系统
作者创建的图像。
Apache Pinot 是一个开源的分布式 OLAP 系统,用于执行低延迟的分析查询。它是在 LinkedIn 创建的,“在工程团队确定没有现成的解决方案能满足社交网络站点的需求后。” Pinot 采用 Lambda 架构,在在线(实时)和离线(历史)数据之间提供统一视图。
自从 Uber 引入 Pinot 以来的两年里,其数据量从几 GB 增长到了几百 TB。随着时间的推移,查询负载也从每秒几百次查询(Queries Per Second)增加到了每秒数万次查询。
Pinot 支持多种索引技术来回答低延迟的 OLAP 查询,例如 倒排索引,范围索引,或 星树索引。Pinot 采用 分而治之 的方式来分布式查询大型表。它按时间边界划分数据,并将其分组成段,同时查询计划并行执行它们。以下是优步决定使用 Pinot 作为其 OLAP 解决方案的原因:
在 Uber,用户利用 Pinot 处理许多实时分析用例。这些用例的主要需求是数据新鲜度和查询延迟。工程师们为 Apache Pinot 贡献了以下功能以满足 Uber 的独特需求:
upsert 操作结合了插入和更新操作。它允许用户更新现有记录,并在记录不存在时插入一条新记录。upsert 在许多用例中是一个常见需求,例如纠正乘车费用或更新配送状态。
作者创建的图像。
Upsert 操作的主要挑战是找到所需的记录位置。为了解决这个问题,Uber 将输入流按主键拆分成多个分区,并将每个分区分发到一个节点进行处理。这意味着同一个节点将处理具有相同键的所有记录。Uber 还开发了一种路由策略,将同一分区的子查询路由到同一个节点。
Pinot 最初缺少一些重要的 SQL 功能,如子查询和连接。Uber 将 Pinot 与 Presto 集成,以支持在 Pinot 上执行标准的 PrestoSQL 查询。
Uber 在将 Pinot 与其他数据生态系统集成方面投入了大量精力,以确保良好的用户体验。
例如,Pinot 与 Uber 的模式服务集成,从输入的 Kafka 主题推断模式并估计数据的基数。Pinot 还与 Flink SQL 集成作为数据目标,因此客户可以构建 SQL 转换查询并将输出消息推送到 Pinot。
归档存储
作者创建的图像。
Uber 使用 HDFS 存储长期数据。来自 Kafka 的大多数 Avro 格式数据以原始日志的形式存储在 HDFS 中。压缩过程将日志合并为 Parquet 格式,然后可以通过处理引擎如 Hive、Presto 或 Spark 访问。这个数据集作为所有分析目的的唯一来源。Uber 还使用此存储进行 Kafka 和 Pinot 的数据回填。此外,其他平台也使用 HDFS 以满足其特定需求。例如:
交互查询层
作者创建的图像。
Uber 采用了 Presto 作为其交互式查询引擎解决方案。Presto 是一个开源的分布式查询引擎,由 Facebook 开发。它被设计用于对大规模数据集进行快速分析查询,通过采用 大规模并行处理(MPP) 引擎,并将所有计算在内存中执行,从而避免将中间结果写入磁盘。
Presto 提供了一个高性能的 I/O 接口的 Connector API,允许连接到多个数据源:Hadoop 数据仓库、RDBMS 或 NoSQL 系统。Uber 为 Presto 构建了一个 Pinot 连接器,以满足实时探索的需求。这样,用户可以在 Apache Pinot 之上执行标准的 PrestoSQL。
Pinot 连接器需要决定哪些物理计划的部分可以推送到 Pinot 层。由于 API 的限制,该连接器的第一个版本仅包括了 谓词下推。Uber 改进了 Presto 的查询计划器,并扩展了 Connector API,以便尽可能多地将操作推送到 Pinot 层。这有助于降低查询延迟并利用 Pinot 的索引功能。
在了解了Uber如何使用开源系统构建实时基础设施之后,我们将讨论Uber生产环境中的一些用例,以及他们如何使用这些系统来实现他们的目标。
优步的动态定价用例是一种动态定价机制,它平衡了可用司机的供应与乘车需求。该用例的整体设计为:
Uber Eats餐厅经理仪表板允许餐厅老板运行slice-and-dice查询,以查看Uber Eats订单中的洞察,例如客户满意度、热门菜单项和服务质量分析。该用例的整体设计:
机器学习在 Uber 中扮演着至关重要的角色,为了确保模型的质量,监测模型预测输出的准确性至关重要。整个用例的设计如下:
UberEats团队需要对来自骑手、餐厅和食客的实时数据执行临时分析查询。这些洞察将用于基于规则的自动化框架中。该框架尤其在疫情期间帮助运营团队根据规定和安全规则运营业务。整个用例的设计如下:
在文章结束前,我将介绍Uber的全活跃策略,如何管理数据回填,以及从Uber中学到的经验。
本节将展示Uber是如何提供业务弹性和连续性的。
Uber 依赖于多区域策略,确保服务在地理位置分布的数据中心中有备份运行,这样如果一个区域中的某个服务不可用,其他区域的服务仍然可以正常运行。这一策略的基础是一个多区域的 Kafka 设置,提供数据冗余和流量延续。
作者创建的图像。
这里是动态定价应用的主备架构示例:
→ 这种方法计算密集度高,因为 Uber 需要在每个区域管理冗余的管道。
Uber 需要回溯时间重新处理数据流,原因有几方面:
Uber 使用 Flink 构建了一个流处理回填解决方案,该解决方案有两种运行模式:
优步(Uber)构建了大部分实时分析栈,依赖于开源组件。依赖这些组件为优步提供了坚实的基础。然而,这仍然会遇到一些挑战:
对于像 Uber 这样的大公司来说,架构演进中常常会看到多个驱动因素,比如新的业务需求或工业趋势。因此,Uber 意识到需要支持快速软件开发,以便每个系统能够迅速演进。
Uber 在以下几个方面努力解决用户扩展的挑战:
Uber 的论文包含了关于实时基础设施、系统设计以及公司如何改进和调整如 Kafka、Pinot 或 Presto 这样的开源解决方案以满足其独特的扩展需求的宝贵经验。
我计划将我的写作主题扩展到系统设计和数据架构等领域,特别是大型科技公司如何管理和开发他们的大数据技术栈,所以敬请期待我未来的文章 ;)
现在是说再见的时候了,下周见。
参考 :
[1] 尤鹏傅和钦梅索曼,优步的实时数据基础设施(2021)。
[2] Mansoor Iqbal, Uber 收入和使用统计数据 (2024).
[3] Arpit Bhayani, 理解读写一致性及其重要性。
[4] Alex Xu, 最多一次、至少一次、恰好一次 (2022).
[5] 徐洪亮,uReplicator:Uber 工程的可扩展且健壮的 Kafka 复制器(2018)。
[6] CelerData,计算架构的优缺点 — 分散/聚合、MapReduce 和 MPP(大规模并行处理) (2023)
[7] Aditi Prakash, 揭秘谓词下推:优化数据库查询指南 (2023).
[8] Dremio,切片和切块分析。
我的通讯是一种每周的博客风格的电子邮件,在其中我会记录从比我聪明的人那里学到的东西。
所以,如果你想和我一起学习和成长,可以在这里订阅:https://vutr.substack.com .