如果我们找不到数据,数据就毫无用处。在优步积累的100多拍字节的数据中查找个人记录,可以让我们执行更新并收集有用的见解,以帮助改善我们的服务,比如向乘客提供更准确的eta,向食客展示他们最喜欢的食物选择。在这样的规模下查询数据并及时提供结果不是一项简单的任务,但它是至关重要的,这样优步的团队就可以获得他们需要的洞察力,为我们的客户提供无缝和神奇的体验。
为了支持这些见解,我们建造了优步大数据平台通过分离存储层和查询层,使每个层都可以独立伸缩。我们将分析数据集存储在HDFS上,注册为外部表,并使用Apache Hive、Presto和Apache Spark等查询引擎提供服务。这个大数据平台为团队提供了可靠和可扩展的分析,以监督我们的服务的准确性和持续改进。
在超级旅行的终身时间内,新信息在旅行创建,旅行持续时间更新和骑车审查更新之类的活动期间将更新到旅行数据库。支持更新需要在修改和持久保留之前查找数据的位置。随着这些查找的规模增加到每秒数百万的运营,我们发现开源键值存储无法满足我们开箱即用的可伸缩性需求-它们要么在吞吐量或正确性上妥协。
为了可靠和一致地找到数据的位置,我们开发了一个称为全局索引的组件。该组件执行记录和查找Hadoop表中数据的位置。它提供了高吞吐量、强一致性和水平可伸缩性,并促进了我们更新Hadoop表中pb级数据的能力。在本文中,我们将扩展我们的现有大数据系列通过解释大规模解决这个问题所涉及的挑战,并分享我们在这个过程中如何利用开源软件。
摄入工作负载类型
优步的Hadoop数据可以广泛分为两种类型:仅附加和附录加上更新。仅附加数据代表不可改变的事件。在优步术语中,不可变的事件可能包括旅行的付款历史。附录加上更新数据显示任何给定时间点的实体的最新状态。例如,在旅行结束时间的实例中,如果跳闸是实体并且结束时间是实体的更新,则结束时间是可以在跳闸完成之前改变的估计。
摄入仅附加的数据不需要上下文,因为每个事件都是独立的。但是,将附加加上的数据摄入到数据集中是不同的。虽然我们仅在实际修改的部分收到更新,但我们仍然需要提供旅行最新和完整的快照。
Append-Plus-Update工作负载
构建数据集通常由两个阶段组成:Bootstrap和Incremental。在引导阶段期间,在短时间内摄取来自上游的大量历史数据。当我们第一次在数据集或需要重新登上数据集以进行维护时,通常会发生此阶段。增量阶段涉及消耗近期,增量上游更改并将它们应用于数据集。此阶段通常占据数据集的剩余生命周期,并确保数据是最新的,因为上游源演变。
在其基本形式中,数据摄取是组织数据以平衡较新数据的有效阅读和写作。有效读数的数据组织涉及将查询模式分解以以读取最小数据的方式分区数据。由于分析数据集倾向于多次读取,因此分区以避免扫描整个数据集。为了高效写入,数据布局在分区中分布在分区中的多个文件中,以在写入期间利用高并行性,并且在任何将来对数据更新的情况下,仅限于包含这些更新的文件的写入脚印。
通过更新提高写入效率的另一个方面是开发一个组件,以便在大数据生态系统中有效查找现有数据的位置。全局索引,一个摄入组件,维护数据布局的簿记信息。此组件需要强的一致性,以将传入数据正确分类为插入或更新。分类后,诸如我们的新行程的插入分组并写入新文件,而更新(例如旅行结束时间)写入全局索引标识的相应预先存在的文件,如图所示1,如下:
以下是全球指数如何为我们的摄取系统带来贡献的架构概述。
一个简单的全局索引解决方案是使用一个经过验证的键值存储,例如HBase或者卡桑德拉。此类键值存储可以支持成千上万的每秒请求对于强烈一致的读/写。
对于大型数据集,在引导阶段的吞吐量要求非常高(每个数据集每秒有数百万个请求),因为需要在相对较短的时间内摄取大量数据。在Uber的大型数据集bootstrap阶段,吞吐量需求是数百万的数量级每秒请求数。在增量阶段但是,吞吐量需求要低得多(每个数据集每秒有数千个请求),除非偶尔出现峰值(可以通过请求速率节流控制)。
高比例索引读/写入,强持续性,合理的索引读/写放大是全局索引的额外要求。如果我们通过单独处理Bootstrap阶段和增量相位索引来划分问题,我们可以使用缩放以解决增量阶段的键值存储索引,但对引导阶段索引不一定。为了理解这是为什么,让我们考虑增量阶段和引导阶段在工作负载方面的区别。
自举期间索引
如果在引导阶段期间,源数据被组织成,以确保输入数据全部插入(如图1所示),不需要全局索引。但是,在增量阶段,我们无法确保输入数据仅由插入组成,因为我们必须定期获取数据,对行的更新可以在任何时间间隔到达。因此,在开始增量阶段之前,需要用索引更新键值存储
我们使用这个属性来设计我们的引导摄取。由于键值存储的请求吞吐量有限,我们从数据集生成索引并批量上传到键值存储,而不发出单独的写请求,从而避免了典型的写路径。
选择合适的钥匙值商店
基于上述简化,键值存储在增量相期间的索引目的的要求是强烈一致的读/写入,能够扩展到每位数据集每秒数千个请求,以及批量上传的可靠方式indexes (i.e., avoiding the limited throughput in the write path).
HBase和Cassandra是优步广泛使用的两个关键值存储。对于我们的全球索引解决方案,我们选择使用HBase原因:
- 与Cassandra不同,HBase只允许一致的读写,因此无需调整一致性参数。
- HBase提供群集中的HBase表的自动重新平衡。主从体系结构允许在群集中获取数据集的传播的全局视图,我们利用将数据集特定的吞吐量自定义为我们的HBase群集。
使用hfiles生成和上传索引
我们以HBase的内部存储文件格式生成索引,称为HFile.,并上传到我们的HBase集群。HBase根据排序的、不重叠的跨键范围对数据进行分区区域服务器在里面hfile文件格式。在每个HFile中,数据根据键值和列名排序。为了以HBase所期望的格式生成hfile,我们使用Apache Spark跨机器集群执行大型的分布式操作。
首先将索引信息作为弹性分布式数据集(RDD)提取,如下图4所示,从引导数据集中如下所示,然后根据使用的值全局排序RDD.sort ()。
我们以这样的方式布置RDD,即每个Apache Spark分区都负责独立编写一个HFile。在每个HFile内,HBase预计下面如图5所示所示的内容,使得它们根据键值和列名来排序。
RDD.flatMapToPair ()然后将转换应用到RDD,以按照图5所示的布局组织数据。然而,这种转变不保留RDD中条目的顺序,所以我们使用RDD.repartitionAndSortWithinPartitions ()不需要对分区进行任何更改。由于已选择每个分区来表示HFILE的内容,因此不改变分区是重要的。然后使用得到的RDD使用hfileoutputformat2.。使用这种方法,对于一些最大的数据集(索引大小有几十兆兆字节),生成HFile只需要不到两个小时。
hfile现在使用一个叫做LoadIncrementalHFiles。如果没有预先存在的区域,则在上载时触发一个名为HFILE分割的过程,如果没有预先存在的区域,它们完全包含HFILE中的键范围,或者HFILE大小大于SET阈值。
分割会严重影响HFile上传延迟,因为这个过程需要重写整个HFile。我们通过读取HFile key ranges,并预先将HBase表分割成与HFile数量一样多的region,从而避免HFile拆分,这样每个HFile就可以放入一个region中。读取HFile键值范围比重写整个文件要便宜几个数量级,因为HFile键值范围存储在头文件块中。对于一些最大的数据集,索引大小在几十兆兆字节,HFile上传需要不到一个小时。
增量摄入期间的索引
一旦生成了索引,每个行键和文件ID之间的映射就不会改变。我们不为摄入批处理中的所有记录写入索引,而是只为插入写入索引。这有助于我们保持对HBase的写请求在一定范围内,并满足我们所需的吞吐量。
节流HBase Access.
正如前面所讨论的,HBase不会超出特定的负载范围。在增量阶段,偶尔会出现负载高峰,因此我们需要限制对HBase的访问。下面的图8显示了多个独立的ingestion作业是如何并发访问HBase的:
我们根据几个影响Hbase请求数的因素来控制从独立的Apache Spark作业到区域服务器的每秒累积写操作:
- 工作并行性:一个job内并发HBase请求数。
- 区域服务器数量:承载特定HBase索引表的服务器数量。
- 输入QPSFraction:数据集累积QP的分数。通常,该数字是数据集中行数的加权平均值,以确保数据集中的QPS公平份额。
- 内部基准QPS:区域服务器可以处理的QPS。
下面的图9显示了对节流算法如何调整以处理更多查询的实验,因为将HBASE区域服务器添加到HBase群集。
系统限制
虽然我们的全球索引系统促进了更大的数据可靠性和一致性,但我们的系统有几个限制,如下所述:
- 他指的是帽定理, HBase提供一致性和分区容错性,但不提供100%的可用性。由于摄入工作不是非常敏感的时间,我们可以有一个更宽松的服务水平协议在HBase宕机的罕见事件中。
- 节流过程假设索引表均匀分布在所有区域服务器上。对于包含少量索引的数据集,这可能不正确。因此,他们最终获得的QPS份额更小,我们通过降低他们的QPSFraction来弥补这一点。
- 如果HBase中的索引损坏,或者由于灾难导致表不可用,则需要一种灾难恢复机制。我们当前的策略是重用前面在从数据集生成索引并上传到新的HBase集群中讨论过的相同过程。
下一步
我们的全球索引解决方案通过优步的大数据平台保持pb级的数据运行,满足我们的sla和要求。然而,有一些改进我们正在考虑:
- 例如,我们通过确保摄入的数据仅追加引导数据,简化了Bootstrap Engestion阶段的全局索引问题,但这可能不适用于所有数据集。因此,我们需要一个解决方案,以规模地解决这个问题。
- 我们希望探索一种索引解决方案,消除外部依赖,如键值存储,如HBase。
请将您的简历发送至hadoop-platform-jobs@uber.com如果您有兴趣与我们合作!





