Apache|Uber 大规模运行 Apache Pinot实践( 二 )


文章插图
该架构的不同组件可以分为三个阶段:
获取
这也成为数据准备阶段,负责使数据可供 Pinot 使用。一般来说,Pinot 可从流数据源(例如 Apache Kafka)以及批处理 / 脱机数据源(例如 Apache Hadoop)中获取数据(请参阅 Pinot 文档)。在 Uber 内部中,我们添加了更多的功能,如下所述:
FlinkSQL
在某些情况下,我们需要对输入的 Kafaka 主题做一些额外的处理,然后 Pinot 才能处理数据。例如:将输入主题与另一个主题 / 表连接起来,或者对一些列值进行预聚合。
对于这种情况,我们依赖 FlinkSQL 的流处理平台(以前称为 Uber 的 AthenaX ,后来回馈给 Apache Flink 社区)。它提供了一个 SQL 接口,用于表示对输入流(Kafaka)的富处理,该输入流被编译成 Apache Flink 作业,并在我们的 YARN 集群上执行。这样一个 FlinkSQL 作业的输出是另一个 Kafaka 主题,它成为 Pinot 的数据源。下面是一个简单的 FlinkSQL 作业示例,它根据设备操作系统和特定的城市 ID 过滤输入的记录。
Apache|Uber 大规模运行 Apache Pinot实践
文章插图
这些经过处理的数据现在可以提取到 Pinot 中,以进一步进行切片和分片。
与实时数据源类似,脱机数据源可以按原样获取,也可以在提取到 Pinot 之前进行预处理。在 Uber 内部,我们依赖另一个名为 Piper(工作流调度系统)的平台获取脱机数据集。与 FlinkSQL 一样,Piper 作业允许用户指定一个 SQL 查询(在本例中为 Hive 查询),用于指定对原始数据所需的处理。在内部,它运行 Spark 作业来运行这个查询,从输出数据创建 Pinot 段(segment)并将其导入到 Pinot 中。如下图所示:
Apache|Uber 大规模运行 Apache Pinot实践
文章插图
Piper 允许用户以给定的频率(例如,每小时或每天)安排这个作业,这反过来定义了将脱机数据集导入到 Pinot 的频率。
存储
下面是 Apache Pinot 核心存储引擎的放大视图:
Apache|Uber 大规模运行 Apache Pinot实践
文章插图
这是 Apache Pinot 以对称配置部署在两个不同地理区域的视图。如图所示,每个区域都有完全相同的组件:
Pinot 集群
每个 Pinot 集群由一个控制器(集群的大脑)、代理(查询处理节点)和服务器(数据节点)组成。Pinot 被设计为从头开始的多租户,它使我们能够将代理和服务器的特定组合分组到一个租户中:一个由特定用例拥有的隔离单元。例如,图中显示的是一个拥有两个租户的 Pinot 集群:Eats 和 Maps。在这个例子中,Maps 租户有两个代理和两个服务器,Maps 数据将均匀分布在这两台服务器上,查询处理将被限制在指定的代理中,从而将其与任何 Eats 的流量隔离开来。
多区域部署
在 Uber 内部,Pinot 表可以配置为:
除了核心 Pinot 存储,我们还利用了另外两个组件。
模式服务
这是 Uber 使用的所有模式的集中存储库。在 Uber 内部,Pinot 大量使用这一点作为所有 Kafaka 模式的真相来源。我们添加了一个定制的 Pinot 解码器,用于在获取过程中获取所需的 Kafaka 模式,并生成相应的 Pinot GenericRow 对象,该对象反过来又用于段生成。接下来,我们还计划使用模式服务来管理 Pinot 模式。
段存储
Pinot 具有段存储的概念,用于对其不可变数据段进行归档。对于任何给定的实时或脱机的 Pinot 表,一旦数据段被密封(基于某些标准),它就变为不可变的。然后将该段存档到段存储中,以便在节点或复制失败期间进行恢复。最初的 Pinot 架构依赖于安装在 Pinot 节点上的符合 POSIX 的文件系统(比如 NFS)。通过添加使用任何通用存储系统(例如 HDFS、Ceph 或 S3)作为段存储的功能,我们对这一功能进行了扩展。有关更多详细信息,请参阅以下章节。
查询
目前在 Uber 内部中有两种查询 Pinot 数据的方式。
Pinot REST Proxy
Apache Pinot 有一个称为代理的组件,用于发出 REST 风格的查询。我们在代理上添加了轻量级层,称为 Pinot REST Proxy。这是一个简单的 Restlet 服务,为应用程序查询任何 Pinot 表提供了一种方便的方式。
正如前面提到的,每个 Pinot 表都有与一个租户相关联,该租户有一组唯一的代理。任何客户端应用程序都必须查询其中一个代理才能访问指定表。这就增加了一些复杂性,因为客户端应用程序需要知道其中的不同租户和代理。使用这个 Restlet 服务,客户端应用程序可以通过一些负载均衡器(在我们的例子中是 haproxy)到达任何一个 REST 代理节点。每个 Pinot REST 代理实例本地缓存 Pinot 路由信息(通过 Apache Helix 获得),它使用这些信息来标识租户、标识代理集,并以异步方式将客户端请求路由到其中一个租户。