- 作者|Luyao Li 等人
- 译者|无明
- 编辑|Debra
- 微信公众号“AI 前线”(ID:ai-front)
指数级业务(和数据)增长
自 2016 年以来,Uber 已在平台上增加了几项新业务,包括 Uber Eats、Uber Freight 和 Jump Bikes。现在,Uber 平台每天发生 1500 万次交易,每月有超过 7500 万活跃用户。在过去的八年中,Uber 已经从一家小型创业公司发展成为一个在全球拥有 18,000 名员工的巨头公司。
随着业务的增长,数据系统和工程架构的复杂性也日益增加。我们的分析引擎中存在数万个表,包括 Hive、Presto 和 Vertica。因为数据太过分散,我们必须对可用的信息有全面的了解,特别是当我们继续添加新业务数据和员工时。2015 年,Uber 开始使用一些手动维护的静态 HTML 文件对这些数据表进行编目。
随着公司的发展,我们需要更新的表的数量和相关元数据的数量也在增长。为了确保我们的数据分析能够跟上公司发展的步伐,我们需要一种更简单、更快捷的方式来更新这些信息。在这种规模和增长速度的背景下,拥有一个可用于发现数据集及其相关元数据的强大系统已经到了刻不容缓的地步。
图1
为了可以更容易发现和探索数据集,我们开发了 Databook。Databook 可用于管理和展示 Uber 数据集的元数据,让 Uber 的员工能够在 Uber 探索、发现和有效利用这些数据。Databook 可以保证数据的上下文(数据的意义、质量等)对于成千上万试图分析它们的人来说是有意义的。简单地说,Databook 元数据让 Uber 的工程师、数据科学家和运营团队从原先的查看原始数据转变为现在的掌握可操作信息。
有了 Databook,我们从手动更新转变为利用高级自动化元数据存储来收集各种经常刷新的元数据。Databook 具备以下几项特性:
可扩展性:易于添加新的元数据、存储和实体。
可访问性:服务可以以编程的方式访问所有元数据。
可伸缩性:支持高吞吐量读取。
其他:跨数据中心读写。
Databook 提供了各种源自 Hive、Vertica、MySQL、Postgres、Cassandra 和其他几种内部存储系统的元数据,包括:表的模式、表 / 列说明、样本数据、统计信息、Lineage、表的新鲜度、SLA 和所有者、个人数据分类。
所有的元数据都可以通过一个集中式 UI 和 RESTful API 来访问。Databook UI 为用户访问元数据提供了一种便利的方式,而 Restful API 则为 Uber 的其他服务和用例提供支持。
虽然现在已经有像 LinkedIn WhereHows 这样的开源解决方案,但在开发 Databook 的过程中,Uber 不支持 Play Framework 和 Gradle。WhereHows 缺乏对跨数据中心读写的支持,而这对于我们来说却至关重要。因此,我们开始构建自己的内部解决方案,并使用 Java 开发,充分利用 Java 的内置功能和成熟的生态系统。
接下来,我们将分享我们是如何创建 Databook 的,以及在这一过程中遇到了哪些挑战。
Databook 架构
Databook 的架构可以分为三个部分:如何收集元数据、如何存储元数据以及如何显示元数据。下图描绘了 Databook 的整体架构:
图2
Databook 将多个源作为输入,存储相关元数据,并通过 RESTful API 输出这些信息。其中 Databook UI 也使用了这些 API。
在设计 Databook 之初,我们必须做出一个重大的决定:存储收集到的元数据还是按需获取?我们的服务需要支持高吞吐量和低延迟读取,如果我们将操作委托给元数据源,所有源都要支持高吞吐量和低延迟读取,这将带来更大的复杂性和更高的风险。例如,用于获取表模式的 Vertica 查询通常需要几秒钟,所以不适合用于可视化。同样,我们的 Hive Metastore 管理着所有的 Hive 元数据,要让它支持高吞吐量读取是有风险的。Databook 可以支持多种不同的元数据源,因此我们决定将元数据保存在 Databook 中。此外,虽然大多数用例需要新的元数据,但它们不需要实时查看元数据变更,所以我们可以进行定时爬取。
我们还将请求服务层与数据收集层分开,每个层都运行在一个单独的进程中,如下图所示:
图3
这种方式将两个层隔离开,从而减少了附带影响。例如,数据收集爬虫作业可能会使用较多的系统资源,从而影响请求服务层 API 的 SLA。此外,与 Databook 的请求服务层相比,数据收集层对中断不太敏感,如果数据收集层关闭,仍然可以提供过时的元数据,从而最大限度地减少对用户的影响。
基于事件的收集与调度收集
我们的下一个挑战是决定改如何最有效地从几个不同的数据源收集元数据。我们考虑了多种方案,包括:创建分布式的容错框架,并利用事件流来近乎实时地检测和调试问题。
我们先是创建了爬虫,定时收集来自各种数据源和微服务的信息,这些数据源和微服务会生成数据集的元数据信息,例如由开源工具 Queryparser 生成的数据表的使用统计信息。(有趣的是,Queryparser 是由 Uber 的数据知识平台团队开发的)。
我们需要以可伸缩的方式频繁收集元数据信息,同时不能阻塞其他爬虫任务。为此,我们将爬虫部署在不同的计算机上,并且需要协调这些分布式的爬虫。我们使用了 Quartz 的分布式模式(由 MySQL 支持)。然而,有两个问题阻碍了这个方案的实现:首先,在多台机器上以集群模式运行 Quartz 需要定期同步 Quartz 时钟,从而增加了外部依赖。其次,在调度程序启动后,持续出现 MySQL 连接不稳定的情况。最后,我们决定不使用 Quartz 的集群模式。
不过,我们仍然继续使用 Quartz 来实现内存调度,以便更轻松、更高效地将任务发布到任务队列中。我们使用了 Uber 开源的任务执行框架 Cherami 来处理任务队列。这个开源工具可以用来解耦分布式系统中的消费者应用程序,让它们能够以异步方式跨多个消费者群组进行通信。有了 Cherami,我们就可以爬虫打包成 Docker 容器,并部署到不同的主机和多个数据中心中。借助 Cherami,我们可以从多个不同来源收集各种元数据,而不会阻塞任何任务,同时将 CPU 和内存消耗保持在理想水平。
虽然我们的爬虫可以爬取大多数元数据类型,但是有时候需要近乎实时地捕获一些元数据,于是我们决定过渡到使用基于事件的架构(Kafka)。有了这个,我们就能够立即检测并调试数据中断。我们的系统还可以捕获到关键的元数据变更,例如数据集 lineage 和新鲜度,如下图所示:
图4
这种架构让我们的系统能够以编程方式触发其他微服务,并近乎实时地向数据用户发起通信。我们仍然使用爬虫执行其他一些任务,比如收集(或刷新)样本数据、限制目标资源请求,以及一些没有必要收集的元数据(有些事件发生时会自动触发其他系统,如数据集使用统计)。
除了近乎实时地轮询和收集元数据之外,Databook UI 还收集来自数据集消费者和生产者的语义信息,例如对表和列的描述。
我们是如何存储元数据的
在 Uber,为了能够进行失效备援,我们的大多数管道都运行在多个集群上。因此,同一张表的某些类型元数据的值(例如延迟和使用统计)可能因集群的不同而不同,它们是特定于集群的。相反,来自用户的元数据则与群集无关:同一张表的描述和所有权信息对于所有群集来说都是一样的。为了正确链接这两种类型的元数据,例如将列描述与所有集群数据表的列关联起来,可以采用两种方法:写入期间链接或读取期间链接。
写入期间链接
在关联特定于群集的元数据和群集无关的元数据时,最直接的策略是在写入期间将元数据链接在一起。例如,当用户将列描述添加到给定的表列时,我们将信息保存到所有集群的表中,如下图所示:
图5
这个方法可确保持久数据处于干净状态。例如,在上图中,如果“列 1”不存在,它将会拒绝请求。不过这样就存在一个问题:在写入期间将与群集无关的元数据链接到特定于群集的元数据,所有特定于群集的元数据必须存在,并且只有一次机会。例如,当图 4 中的描述被触发时,只有集群 1 有“列 1”,因此对集群 2 的写入会失败。稍后,群集 2 中同一个表的模式会被更新,但已经没有链接元数据的机会了,除非我们进行定时重试,否则这个描述将永远不可用,从而让系统变得更复杂。下图描述了这种场景:
图6
读取期间链接
另一种方法是在读取期间链接群集无关和特定于群集的元数据。这个方法解决了写入期间链接会丢失元数据的问题,因为只要特定于群集的元数据存在,这两种类型的元数据就可以在读取期间进行链接。模式更新后,“列 1”就会出现,并在用户读取时被合并,如下图所示:
图 7
存储选择
MySQL 最初用于为 Databook 的后端提供支持,因为用它开发速度快,还可以通过 Uber 的基础设施门户进行自动配置。不过,当涉及多数据中心时,共享 MySQL 集群的效果并不理想,原因有三:
单个主节点:首先,Uber 只支持单个主节点,导致其他数据中心的写入较慢(每次写入增加了约 70 毫秒)。
手动切换主节点:其次,当时还不支持自动切换主节点。因此,如果主节点宕机,需要几小时才能切换到新的主节点。
数据量:我们弃用 MySQL 的另一个原因是 Uber 的大量数据。我们打算保留所有的历史变更记录,并希望我们的系统能够支持未来的扩展,而无需在集群维护上花费太多时间。
鉴于这些原因,我们使用 Cassandra 取代了 MySQL,因为它提供了强大的 XDC 复制支持,可以在几乎不增加延迟的情况下从多个数据中心写入数据。而且 Cassandra 是线性可扩展的,可以适应 Uber 不断增长的数据量。
我们是如何提供数据的
Databook 提供了两种访问元数据的方法:RESTful API 和 UI 控制台。Databook 的 RESTful API 由 Dropwizard 提供支持,Dropwizard 是一个用于开发高性能 RESTful Web 服务的 Java 框架,可部署在多台计算机上,并通过 Uber 的内部请求转发服务进行负载均衡。
在 Uber,大部分服务通过编程的方式访问 Databook 的数据。例如,我们的查询解析 / 重写服务就依赖于 Databook 的表模式信息。API 可以支持高吞吐量读取,并且支持水平扩展,每秒的峰值查询大约为 1,500。UI 控制台使用 React.js、Redux 以及 D3.js 开发,整个公司的工程师、数据科学家、数据分析师和运营团队都在用它诊断数据质量问题以及识别和探索相关数据集。
搜索
搜索是 Databook UI 的一项重要功能,用户因此能够轻松访问和浏览表的元数据。我们使用 Elasticsearch 作为全索引搜索引擎,Elasticsearch 会从 Cassandra 同步数据。用户可以使用 Databook 进行跨维度搜索,例如名称、所有者、列和嵌套列,如下图所示,从而实现更及时、更准确的数据分析:
图 8
Databook 的下一个篇章
有了 Databook,Uber 的元数据比以往更具可操作性和实用性,但我们仍在努力通过构建更强大的功能来扩展我们的影响力。我们希望增加的功能包括利用机器学习模型生成数据洞见,以及创建高级的问题检测、预防和缓解机制。
英文原文:
https://eng.uber.com/databook/
本文为专栏文章,来自:AI前线,内容观点不代表本站立场,如若转载请联系专栏作者,本文链接:https://www.afenxi.com/59895.html 。