介绍
优步拥有一个复杂的市场,包括乘客、司机、食客、餐厅等。在全球范围内运营这个市场需要实时情报和决策。例如,识别延迟的Uber Eats订单或丢弃的购物车有助于我们的社区运营团队采取纠正措施。拥有不同事件(如消费者需求、司机可用性或城市中发生的旅行)的实时仪表板对于日常运营、事件分类和财务情报至关重要。
在过去的几年里,我们已经建立了一个自助服务平台,以便在超优步跨越不同的案例,以及许多其他人。该平台的核心构建块是Apache Pinot - 一种用于在TBRABYTES-SCALE数据上执行低延迟分析查询的分布式在线分析处理(OLAP)系统。在本文中,我们介绍了这个平台的详细信息以及它如何适应优步生态系统。我们专注于优步中的Pinot的演变,以及我们如何从少数用例中缩放到多集群,所有主动部署都能用毫秒延迟查询Terabyte-Scale数据的数百个使用情况。
用例概述
上图描绘了实时分析用例的典型要求。优步中的不同用例可以逐步划入以下广泛类别:
- 仪表板
- 分析应用程序
- 近实时勘探
仪表板
优步的很多工程团队都使用比诺来为各自的产品定制仪表盘。其中一个例子是Uber Eats餐厅经理:
这款仪表板使餐厅的所有者能够从优步洞察中获取有关客户满意度,流行菜单项,销售和服务质量分析的订单。Pinot以不同的方式开展和切割原始数据,并支持低延迟查询,以为餐厅所有者构建丰富的体验。
类似地,我们的城市运营团队已经构建了定制的仪表板,利用Pinot结合实时和历史数据的能力来获得关于供求、异常事件(例如,最后5分钟内的延迟订单)、实时订单等的指标。这是我们日常操作的一个关键工具,有助于及早发现问题。
分析应用程序
另一类用例源于执行分析查询的必要性,作为许多后端服务的一部分。此类用例的主要区别要求是数据新鲜度和查询延迟,需要实际在自然界中。例如,实时识别Uber骑手分组的地理热点对于良好的用户体验至关重要。同样,识别骑手取消或放弃的优步立即饮用车,可以快速纠正措施(以消息传递/激励措施的形式)。
近实时探索
数据探索通常是在Hadoop等传统批量和仓储系统上进行的。然而,有许多实例需要用户可以在实时数据上执行复杂的SQL查询。例如,工程师通常需要通过加入MicroServices记录的各种事件来进行分类事件。在其他情况下,实时事件可能需要与坐在蜂巢处的批量数据集合联接。在优步中,我们在Apache Pinot之上提供了一个富有的(Presto)SQL接口,以解锁底层实时数据集的探索。此外,此界面无缝地与我们所有的内部商业智能工具(如DashBuilder)合作,这对所有客户都非常有用。例如,这是一个简单的森伯斯特图表,显示了优步的细分在五分钟内,对于由作业状态分组的给定区域。这是通过在几秒钟内运行的pinot上运行的presto查询来使用dashbuilder构建。
今天,数以百计的业务关键用例在生产中由Apache Pinot提供动力。在过去的几年里,我们已经从运行一个小型的10个节点集群发展到每个区域运行数百个节点。Pinot管理的数据总量已经从早期的几十gb增加到今天的几十tb。同样,每个地区的QPS也增加了30倍(目前生产的QPS有数千个)。
在以下部分中,我们描述了我们平台的详细信息,讨论了优步贡献,优先权Uber已经对Apache Pinot进行了详细,并在规模上运行该平台时的经验教训。
Pinot平台在优步
为了服务于这样的用例,我们已经构建了一个围绕Apache Pinot的自助服务平台,如下图所示
此架构的不同组件可以分为3个阶段:
- 摄入
- 存储
- 询问
摄入
这也被称为数据准备阶段,负责使数据可供比诺消费。一般来说,Pinot可以从流数据源(如Apache Kafka)和批量/离线数据源(如Apache Hadoop (看这里的皮诺文件)。在优步内部,我们添加了更多如下所述的功能:
- 实时来源:成千上万的微服务将他们的日志和事件持续写入主题Apache卡夫卡(PUB子系统)。这些主题中的许多可以通过Pinot直接消耗,以使数据以实时方式查询。例如,用户需求跨越不同尺寸的度量(如时间,位置或产品线),可以从用户眼球Kafka流摄取的黑比特表中容易地计算。
FlinkSQL
在某些情况下,我们需要在Pinot消耗数据之前对输入Kafka主题进行一些额外的处理。例如:将输入主题与另一个主题/表一起加入,或者预计某些列值。
对于这种情况,我们依赖于流处理平台FlinkSQL(以前称为雅典雅典来自优步,后来贡献回Apache Flink社区)。它提供了一个用于表达在输入流(Kafka主题)上的丰富处理的SQL接口,它被编译成Apache Flink作业并在我们的纱线上执行。这种flinksql作业的输出是另一个Kafka主题,成为Pinot的数据源。这是一个简单的flinksql作业的一个例子,其通过设备操作系统和特定城市ID过滤传入的记录。
2。离线消息:在Uber的数据生态系统中,大多数重要的Kafka主题也被纳入HDFS。这成为了皮诺历史数据的重要来源。Pinot提供了一个强大的功能,可以将实时和离线数据集合并到同一个表中,为用户提供一个逻辑视图(在这里查看更多细节)。这在很多方面都很有用:
- 数据纠正:在许多情况下,来自Kafka的实时数据可能丢失或不正确。主题的所有者通常在稍后的时间点纠正此问题,最终在脱机数据集中反映。然后,Pinot可以消耗静电的离线数据集并覆盖不一致的实时数据 - 提高我们分析的整体准确性。
- 批量数据加载:在某些情况下,我们需要使用至少3个月的数据引导Pinot表。直接从Kafka这样做将花费太长。具有直接从离线源进行此类数据集的方法非常方便。
- 低延迟服务脱机数据集:在许多情况下,工程师和数据科学家需要对ETL作业的输出执行实时分析查询(坐在一些临时蜂巢表中)。传统的查询引擎,如hive和presto是非常强大的,但通常不能实现低延迟(子二粒度)。这让人们转向Pinot以导入这些数据集。
- 复杂处理:在很多与机器学习相关的用例中,我们需要运行复杂的算法来处理在FlinkSQL中难以表达的计算模型。在这种情况下,工程师和数据科学家可以编写一个Spark作业来计算这些模型,然后将这些数据输入Pinot,用于在线服务。
与实时数据源类似,离线数据源可以按原样摄入,或者在被摄入到Pinot之前进行预处理。在优步内部,我们依赖另一个平台吹笛者(工作流程调度系统),用于摄取脱机数据集。与FlinkSQL一样,Piper作业允许用户指定用于指定原始数据所需的处理的SQL查询(在这种情况下,一个Hive查询)。在内部,它运行一个Spark作业来运行此查询,从输出数据中创建Pinot段并将其导入Pinot。这在下图中描绘了:
PIPER允许用户在给定的频率(例如,每小时或每日)处将此作业安排,这又定义了离线数据集的频率导入到黑谱中。
存储
以下是Apache Pinot核心存储引擎的放大图:
这是在对称配置中在两个不同的地理区域中部署的Apache Pinot的视图。如图所示,每个区域具有完全相同的组件:
- 黑比诺集群
- 卡夫卡
- HDFS.
黑比诺集群
每个Pinot集群由控制器(集群的大脑)、代理(查询处理节点)和服务器(数据节点)组成。Pinot从一开始就被设计为多租户,它使我们能够将代理和服务器的特定组合组合到一个租户中——一个由特定用例拥有的隔离单元。例如,图中显示了一个有两个租户的Pinot集群:Eats和Maps。在本例中,map租户有两个代理和两个服务器。映射数据将均匀地分布在这两个服务器上,查询处理将限制在指定的代理上,从而将其与任何Eats流量隔离开来。
多区部署
在优步,一张比诺表可以配置为:
- 地区的地方:在这种情况下,Pinot表从本地Kafka和HDFS实例中摄取数据。换句话说,所有传入数据都由属于该特定区域的服务生成。这对于对区域局部数据具有亲和力的分析用例是有用的。例如,有关Live优步的指标吃订单或延迟订单仅对特定区域有意义。通常,当数据新鲜度非常重要时,使用此配置。
- 全球:这对于对数据的全局视图感兴趣的分析用例是有用的。在这种情况下,数据分别由Kafka和HDFS聚合。换句话说,每个区域的Kafka主题聚合将拥有来自各个区域本地Kafka主题的所有数据(这些数据依次被摄入到Pinot中)。当我们想要了解全球所有旅行(例如,从收入、销售或事件)时,可以使用此配置。虽然这在Kafka和HDFS数据集的聚合方面设置起来比较复杂,但它会自动使Pinot active-active。因此,任何一个地区的失败都不会影响比诺的可用性。
除了核心黑孔存储外,我们还利用了2个其他组件。
模式服务
这是优步使用的所有模式的集中存储库。在Uber内部,比诺大量使用这一点作为所有卡夫卡模式的真相来源。我们已经添加了一个自定义Pinot解码器,用于在摄取过程中获取所需的Kafka模式,并生成相应的Pinot GenericRow对象,该对象反过来用于段生成。接下来,我们计划使用模式服务来管理Pinot模式。
部分商店
Pinot有一个段存储的概念,用于存档不可变的数据段。对于任何给定的实时或离线Pinot表,一旦数据段被密封(基于特定的标准),它就变成不可变的。然后将此段归档到段存储中,以便在节点或复制失败时进行恢复。最初的Pinot架构依赖于使用兼容POSIX的文件系统(如NFS)挂载到Pinot节点上以实现这一目的。通过添加使用任何通用存储系统(例如HDFS、Ceph或S3)作为段存储的能力,我们已经对此进行了扩展。请参阅下面的部分了解更多细节。
询问
目前,有两种方法可以在优步中查询Pinot数据。
黑比诺其他代理
Apache Pinot有一个称为代理的组件,用于发出RESTful查询。我们在称为Pinot Rest Proxy的经纪人的顶部添加了一层轻量级图层。这是一个简单的重启服务,为应用程序提供了一个方便的方法来查询任何Pinot表。
如前所述,每个顶点表与具有独特经纪人集合的租户相关联。任何客户端应用程序都必须查询其中一个代理才能到达指定的表。这增加了一些复杂性,因为客户端应用程序需要了解其不同的租户和经纪人。使用此新的重启服务,客户端应用程序可以通过一些负载均衡器(在我们的情况下,HAProxy)到达任何一个REST代理节点。每个Pinot REST代理实例在本地缓存Pinot路由信息(通过Apache Helix获得)。它使用此信息来标识租户,识别代理集,并以异步方式将客户端请求路由到其中一个。
每个Pinot rest代理实例中的本地缓存元数据在各种场景中都很有用。Piper (Spark)作业可以查询rest代理来获取表和模式信息,而不是Pinot控制器。这减少了控制器上的负载,并将其与请求峰值隔离开来。
Pinot REST代理目前被仪表板和分析应用程序使用情况大量使用。
普拉斯托
最近,我们已经完成了很多工作来将Presto与Pinot集成,这允许用户使用标准的PrestosQL来查询Pinot。我们最初专注于实时探索用例以及某些分析应用程序。但是,经过多次优化和多个季度的生产经验后,我们目前正在船上实时仪表板和应用程序使用情况。我们的长期计划是用presto取代Pinot Rest代理。有关详细信息,请参阅完整SQL支持下的下面的部分。
优步的贡献
优步的Pinot团队在提高整体可靠性和查询灵活性方面做出了四个主要贡献。
自助服务
在Pinot平台的初始日期,船上新用例是一个非常手动过程。我们的一个清晰的工程师必须与客户一起坐下来了解要求,提出架构,表配置,并估计为使用该用例所需的容量。当然,顾客开始与Pinot一起玩的周转时间可能是从三天到一周的任何地方。为了使平台自行服务,我们投资了以下领域。
- 模式推理
我们添加了从输入的Kafka主题或使用Avro模式创建的Parquet数据文件自动派生Pinot模式的能力。在较高的层次上,该实用程序将Avro字段转换为Pinot列类型,并自动选择其中一个字段作为时间戳列。在某些情况下,它还将Avro记录压平为单个的Pinot柱类型。这种自动转换适用于超过80%的输入Kafka或Parquet数据集,节省了大量的手动工作。
- FlinkSQL推
我们与FlinkSQL紧密集成,使客户能够将Pinot视为一个“数据接收器”。客户创建一个新的FlinkSQL作业,定义一个SQL转换查询,定义输入和输出Kafka主题,然后“push”到Pinot。
在这种情况下,从输出Kafka主题推断出Pinot模式。一旦FlinkSQL作业开始执行,这将在Pinot暂存环境中自动创建一个表。
- 吹笛者推
类似地,我们添加了从“Hive to Avro Converter”Spark作业的输出派生Pinot模式的能力,并在推入数据之前自动在分级Pinot集群中创建表和模式。
每个这样的表都是在一个分段环境中使用最小配置创建的。这允许用户在几分钟内开始向Pinot发出查询或构建定制的BI仪表板。在分段阶段,表将经历几轮迭代,如模式演变、向相应列添加专用索引(例如星树、排序或倒序)和验证用户查询。登台环境的内存和磁盘使用情况很好地说明了生产需求。我们要求每个用例在投入生产前至少要经过24小时的审查。
完整的sql支持
如前所述,我们已经集成了Pinot和Presto,以便在这些数据集上启用标准的PrestoSQL查询。这种组合效果很好,因为我们将Pinot的二级数据新鲜度与Presto在执行复杂查询时的灵活性结合起来。此外,谓词下推和聚合函数下推使我们能够为这样的PrestoSQL查询实现亚秒级的查询延迟——这在标准的后端如HDFS/Hive上是不可能做到的。请在我们之前的文章中找到这个工作的细节://m.lharmeroult.com/engineering-sql-support-on-apache-pinot/
HDFS Deep Stop for Pinot Segments
黑比诺的原始设计为了实时流摄取(又名。LLC.)需要将本地安装的文件系统到Pinot控制器,用于存储Pinot段。在Pinot Server在其副本后面落后或在节点故障后重建的情况下,它允许服务器从中央商店下载段。原始设计通过将网络文件系统(NFS)安装到控制器来解决此容量问题。
与许多其他用户的Pinot一样,优步不操作NFS,因此无法使用原始LLC.设计。为了解决这个问题,我们与领英工程师一起加强了分段完井阶段这样,它就可以与深存储或外部存储服务,如HDFS或Amazon S3一起工作。今天,Uber的Pinot实时摄入管道使用HDFS作为其深度存储,包含数百个Pinot表中的片段。
架构演变
我们的团队确定了Pinot中的架构演变的重要问题。在实时比比表(具体地,从流数据源摄取)的情况下,不完全支持将新列添加到现有模式。虽然较旧的数据段正确地反映出来,但是在最活动的数据段中不可见新列,导致查询失败。我们可以在此处找到本关键问题的修复:https://github.com/apache/incubator-pinot/issues/4225
经验教训
我们已经学到了很多东西,同时在优步中缩放了我们的清晰用例。本节中的许多课程来自解决运营,部署,内存管理和监控中的越来越多的痛苦。
易操作性
易于操作是必要的缩放皮诺使用。当更多的用例出现在Pinot集群中时,我们希望将集群管理的开销降到最低。值得庆幸的是,比诺有几个开箱即用的特性,使操作和管理更容易。
多租户Pinot集群
在规模上运行分析系统的核心要求之一是多租户。Pinot提供了本地多租户支持,它表明了在运营中的巨大价值,特别是在中断缓解中。通过一流的租户支持,表格可以在单个租户名称下逻辑分组,并分配给该租户的主机。这提供了强烈的隔离,避免了嘈杂的邻居问题。当出现问题时,如糟糕的查询按下服务器,我们可以将影响限制在其租户上,而不是违反其他租户的SLA。
集群扩展的方便性
Pinot附带段分配策略,因此可以在主机中均匀地分发段,包括新添加的段。这极大地简化了集群/租户的扩张努力。我们需要做的就是提供新的Pinot Server主机并将其添加到所需的租户。它将自动开始获得新的段。
优步在开源Pinot基础上改进的一个方面是添加了与租户相关的JMX指标。在优步的规模下,我们的比诺集群拥有大约1000张桌子和数百个比诺服务器和经纪人。这使得皮诺管理人员难以监控;一个典型的场景是,一个表存在查询性能问题,但这可能是由同一租户上的其他一些表引起的。使用Pinot租户指标,可以将服务器分组为12个租户,以检查每个租户的资源使用情况和查询性能。它使Pinot服务器/代理的管理和问题的分类更容易。
Pinot提供的另一个有价值的功能是段存储中的段备份(提到在这里),例如远程HDFS集群或云存储。该特性极大地减少了更换服务器节点所需的操作工作,有助于处理在大量计算机池中发生的硬件故障。通过深度存储备份,新添加的主机可以在无需人工干预的情况下下载和恢复数据,并在片段完全下载后自动为流量服务。
内存开销
通常,给定的Pinot控制器或服务器会经历垃圾收集(GC)暂停。根据GC暂停的严重程度,这可能导致性能下降或停机。例如,如果领导控制器持续经历完整的GC暂停,它将无法创建新的段,从而停止对该集群中所有表的摄取。如果Pinot服务器经历了完全GC,它可能会导致查询延迟峰值和属于该节点的所有表的查询结果不一致。以下是这些问题背后的典型原因。
大扫描
在Pinot顶部启用Presto查询后,我们注意到错误的查询通常会压倒Pinot Server。例如,如果用户尝试做一个选择 *查询一个巨大的时间范围(或者没有任何时间范围谓词),它将导致Pinot服务器使用大量内存(我们的设置使用内存索引而不是mmap),并最终导致完全的GC暂停。有几种方法可以减少如此大的扫描的影响:
- 用关闭堆索引这可能会影响整体查询延迟。
- 限制Pinot查询调度程序配置中的查询并行性。减少这个限制还可以减少相应查询造成的内存压力。当然,这也会影响查询的总体吞吐量。
一般来说,为了避免这样的问题——我们将这样的临时查询用例隔离到一个单独的租户。
Apache Helix指数错误
Pinot控制器是整个Pinot集群的控制平面。根据设计,它需要有限的堆空间,因为它不托管或服务数据。当我们的Pinot集群变得更大,包含更多的表和包含更多段的表时,我们发现Pinot控制器的堆使用量比默认值4GB大得多。随后出现了主要的Java GC事件,从而导致整个集群中断。通过对Pinot控制器的堆分析,我们发现这个问题与a直方图度量保存的螺旋lib使用的Pinot控制器。直方图度量使用默认的1小时滑动窗口,这意味着对于繁忙的生产控制器,内存中会保存太多的事件数据点。我们的贡献使固定到Apache Helix,这样就可以配置滑动窗口长度以减少内存占用。
其他原因
随着各种各样的优步团队使用各种各样的Pinot索引和查询模式,我们也看到了其他内存开销的情况:
- 涉及HyperLoglog对象(用于近似不同计数)的查询对输入大小更改敏感;即使Pinot Servers可以摄取这些对象,所需的时间性能也会使用2-5倍的数据卷因GC活动而降低了很多。
- 在子句中具有大数字(数千个)值的查询也可能导致GC,因为这些查询在服务器的多个表段上运行 - 增加内存使用情况。
- 查询量的激增是正常流量的2-3倍或更多(通常由区域故障转移引起)可能导致严重的gc
段管理
由于我们的Pinot Clusters和整体数据量的规模,我们迄今为止遇到了几种挑战。
每个服务器的段太多
随着数据规模的增长,我们也经历了由于细分过多而导致的一些问题。黑比诺利用Apache螺旋在Apache Zookeeper进行群集管理。例如,当从离线转换到在线的服务器时,Pinot将通过螺旋传播状态转换消息以通知其他实例。这种状态转换消息的数量与服务器上的段的数量成比例。当服务器托管过多的段时,螺旋上可能存在状态转换消息的峰值,从而导致许多Zookeeper节点。如果ZookeEper节点的数量超出缓冲阈值,则Pinot Server和Controller将崩溃。要解决这个问题,我们补充说消息节流对于Pinot控制器来压平状态转换浪涌。
热点
我们面临的另一个挑战是由于分段分配策略而产生的潜在热点。默认情况下,Pinot均衡服务器之间的段,通过分配一个新的段给最小分配的主机。因此,在集群扩展的情况下,最近的段都可以在新添加的服务器中创建。为了缓解这个问题,我们在集群扩展后运行表rebalance。
从段存储分离出来
在我们的分部Deep Store的运营期间,我们当时确定了当前LLC协议的两个主要问题:
- 深度存储是实时摄入流的单点故障
- 所有细分市场上传和下载通过Pinot控制器
第一个问题是一个特别严重的问题,因为我们的许多用户希望在数据新鲜度上有较高的SLA(第99百分位的SLA不超过5分钟)。在Uber内部,我们已经看到了一些HDFS由于维护或停机而不可用一个小时左右的情况。这违反了我们所有重要的实时表的SLA。在实践中,HDFS有自己的SLA,可以独立于Pinot失败。为了解决这种严格的依赖问题,我们提出了一个重大改进超过LLC.因此,即使深度商店已关闭几个小时,它也可以继续实时摄取。在Deep Store停机期间,该提案利用了对等服务器存储来下载段。该提案由社区批准,代码已完成,目前正在测试。
结论
总的来说,我们与阿帕奇比诺的经验是伟大的。在优步内部,它已经成为大规模解决实时分析用例的关键技术。内存高效索引和柱压缩有助于降低存储成本。内置的多租户特性和易于维护的节点和租户低运营成本。此外,皮诺周围的阿帕奇社区非常欢迎和高度参与。我们继续投资于Pinot,并计划与社区合作未来的项目,如Pinot Upserts,联邦段存储和查询,智能索引等。
关于Apache Pinot.
如果您有兴趣了解Apache Pinot的更多信息,请访问










