likes
comments
collection
share

流数据库——物化视图在前几章中,我们只简要提到了物化视图。在真正理解流数据库之前,物化视图将是你需要掌握的最重要的概念。

作者站长头像
站长
· 阅读数 34

在前几章中,我们只简要提到了物化视图。在真正理解流数据库之前,物化视图将是你需要掌握的最重要的概念。物化视图首次引入是在 1990 年代初期。最初,它们作为某些 OLTP 数据库中的一种功能被开发出来,目的是通过预计算并存储复杂查询的结果来提高查询性能。物化视图提供了一种将查询结果存储为物理表的方法,这些表可以定期或按需刷新,以确保数据是最新的。这种方法有助于减少重复执行昂贵查询的开销,用户可以直接从物化视图中获取数据,而无需每次都执行复杂的查询。

在流处理中,物化视图不仅是定期或按需更新的。它们总是以异步方式在后台刷新。随着新数据的到来,物化视图会立即更新,并将结果存储起来。我们在前几章中已经强调了这一模式。异步刷新与流处理紧密相关,而同步刷新则对应批处理。

Martin Kleppmann 的视频《Turning the Database Inside-Out》(将数据库颠倒过来)中描述了物化视图,不仅是预处理的数据,还直接从事务日志的写操作中构建。物化视图通过引入预计算并持续、增量更新的查询结果这一概念,对流处理产生了重大影响。物化视图解决了一些流处理中的挑战,并带来了诸如改进查询性能、减少数据重复以及简化分析等益处。

视图、物化视图和增量更新

通过物化视图,生成特定查询结果的处理逻辑与主流处理管道分离开来。这种分离可以带来更加模块化和易于管理的代码,使得维护和扩展流处理系统变得更加简单。

要理解物化视图,我们首先需要理解传统视图。传统视图和物化视图都存在于数据库中。传统视图(或简称为“视图”)是通过一个 SQL 语句定义的,当客户端从视图中选择数据时,这个 SQL 语句才会被执行。视图的结果并不会被存储,这增加了从视图中选择数据的查询的延迟,因为结果并未预处理。为了更好地理解这一点,我们再次使用一个类比:你有一只名叫 Simon 的聪明花栗鼠(见图 4-1)。

流数据库——物化视图在前几章中,我们只简要提到了物化视图。在真正理解流数据库之前,物化视图将是你需要掌握的最重要的概念。

你问Simon:“我院子里现在有多少坚果?”Simon跑到你的院子里数了数坚果,然后回来告诉你数量。当你再次问Simon院子里的坚果数量时,他又一次跑出去数所有的坚果,然后给你一个数字。每次你都得等Simon数完坚果之后才能得到数量,即使这个数量没有发生变化。这就类似于传统视图,可以用数学表示如图4-2所示。

流数据库——物化视图在前几章中,我们只简要提到了物化视图。在真正理解流数据库之前,物化视图将是你需要掌握的最重要的概念。

你觉得这种方法效率不高。于是,你指示Simon将坚果的总数写在纸上,并把它存放在一个盒子里。然后你再次询问Simon有多少坚果,但他无法回答,因为他太忙于观察院子里坚果数量的变化了。所以你雇佣了另一只不如Simon聪明的松鼠,只需要告诉你盒子里的数字。我们可以称他为Alvin。这种类比类似于物化视图。

在这个类比中,松鼠代表了SQL语句。第二种情况下的盒子代表了物化视图的存储,用来保存已经预先计算的结果。在这个场景中,负责预先计算坚果数量的Simon比负责呈现盒子里数字的Alvin更聪明(如图4-3所示)。Alvin可以以低延迟的方式呈现数字,并且可以轻松地同时为多个客户端提供服务。

流数据库——物化视图在前几章中,我们只简要提到了物化视图。在真正理解流数据库之前,物化视图将是你需要掌握的最重要的概念。

物化视图类比中的一个重要部分是,Simon并不是从第一个到最后一个坚果重新数一遍,而是观察坚果数量的增量变化。这包括了从院子里移走了多少坚果以及有多少坚果新增(或者从树上掉下来)。

增量变化指的是对现有数据进行小而有针对性的更改,而不是从头重新计算整个数据集。这些更新通常用于保持数据的一致性和及时性,而不需要重新处理整个数据集的计算开销。

增量函数可以用数学方式表示,如图4-4所示。X代表院子里当前的坚果数量,∆X代表坚果数量的增量变化。X已经存储,而聪明的松鼠Simon捕捉到∆X,然后将其与当前状态X相加,以得出下一个状态。

为了捕捉这些增量变化,Simon必须始终异步观察新的变化——这与流处理环境中的情景类似。

流数据库——物化视图在前几章中,我们只简要提到了物化视图。在真正理解流数据库之前,物化视图将是你需要掌握的最重要的概念。

回想一下第1章中的CDC(变更数据捕获)。CDC 是增量变化的一个典型例子。回顾一下,CDC是一种通过读取OLTP数据库中的写前日志(WAL)来捕捉和跟踪随时间推移对数据库或数据源所做更改的技术。与从头开始处理整个数据集不同,CDC 仅识别和捕捉增量变化:插入、更新和删除。

变更数据捕获(CDC)

CDC(变更数据捕获)与物化视图之间有着紧密的关系。物化视图通过监视增量变化并存储结果,完成了预计算的繁重工作。而在此之前,CDC 会从 OLTP 数据库的写前日志(WAL)中捕获这些增量变化。这意味着我们可以使用物化视图来预处理包含增量变化的 CDC 数据。

回到我们之前的松鼠类比中,我们让 Simon 提供院子里坚果的数量。现在我们进一步扩展这个例子,假设院子里有许多不同种类的坚果。每种坚果都有以下属性:

  • 颜色
  • 位置(纬度,经度)

随着时间的推移,坚果可能会随着变老而改变颜色,或被其他动物移动或移除。Simon 通过在纸上的坚果清单中插入、更新或删除每颗坚果,来跟踪这些变化。因此,当客户端查询坚果清单时,它们只会看到院子里每颗坚果的最新状态。

我们在图 4-5 中技术性地展示了这一场景。图中有一些关键点:

  • 左侧最远处的主数据库/OLTP 数据库中的 WAL 被复制以创建主数据库的副本。
  • 通过 CDC 连接器,WAL 也被写入流处理平台的一个主题中。这个主题发布了主数据库的 WAL,以便其他系统可以订阅。
  • Sink 连接器可以从主题中消费数据,并在其他数据库系统中构建副本。
  • 流处理器可以在其缓存中构建相同的数据库副本。

流数据库——物化视图在前几章中,我们只简要提到了物化视图。在真正理解流数据库之前,物化视图将是你需要掌握的最重要的概念。

通过这种技术,您可以在任何下游数据存储或流处理引擎中构建一个与原始 OLTP 数据库相匹配的副本,这些数据来自面向用户的应用程序。我们主要关注流处理引擎,因为它能满足实时性需求,并且不强制使用批处理语义。

在第 3 章中,我们介绍了推(push)和拉(pull)查询。如果我们应用之前的松鼠类比,Simon 就是推查询,而 Alvin 则是拉查询。

注意

当我们谈论聪明的(Simon)和简单的(Alvin)松鼠时,我们指的是 SQL 语句的复杂性。Simon 能够进行复杂的转换和聚合,而 Alvin 则执行低复杂度的简单 SQL 查找查询。

推查询与拉查询

让我们扩展之前的松鼠类比。通过利用推查询(即 Simon),我们可以从 Alvin 那里查询结果,而不必承担同步计算结果时产生的延迟。

回到最初的案例中,Simon 负责计算院子里坚果的数量。回顾一下,Simon 是异步工作的,持续观察坚果数量的变化,并将更新存储在箱子里。从某种意义上说,Simon 将结果推送到箱子里。Alvin 则是同步地将箱子里的内容提供给客户端。同样地,在查询时,Alvin 从箱子里取出结果并提供给客户端。总结如下:

  • Simon 是一个异步运行的推查询。
  • Alvin 是一个同步运行的拉查询。

Simon 负责大部分计算工作,以便 Alvin 能在被查询时以最低延迟提供结果。这种方法非常有效,但有一个缺点:调用拉查询的客户端在提出更深层次问题时没有太多灵活性。客户端只能使用坚果的数量来构建实时见解。如果客户端想要获取平均数、最大数,或是进行多表连接呢?在这种情况下,推查询会限制客户端提出更深入问题的能力。

在图 4-6 中,为了增加查询的灵活性,您需要在延迟和灵活性之间做出权衡,因为您正在迫使服务引擎进行更多的计算工作。如果一个面向用户的应用程序调用了查询,您希望它以最低的延迟执行,因为假设会有更多的最终用户使用该应用程序。相反,如果您希望获得最高的灵活性,以便能够深入分析数据以获得洞察,那么您应该预期只有少数专家用户会执行这些查询。

流数据库——物化视图在前几章中,我们只简要提到了物化视图。在真正理解流数据库之前,物化视图将是你需要掌握的最重要的概念。

如果仔细思考一下,那些需要最低延迟的应用程序将最能从推查询中获益,而不是拉查询。图 4-7 展示了如何在推查询和拉查询之间找到平衡。

中间的盒子代表物化视图。它在推查询的繁重计算任务和拉查询的灵活性之间取得平衡。如何在推查询和拉查询之间找到平衡取决于您的用例。如果盒子沿着线向下移动,物化视图提供的查询灵活性会减少,但性能更高。相反,如果盒子向上移动,拉查询的灵活性会增加,但查询执行的延迟也会更高。推查询和拉查询结合在一起,帮助您在延迟和灵活性之间找到合适的平衡(见图 4-8)。

流数据库——物化视图在前几章中,我们只简要提到了物化视图。在真正理解流数据库之前,物化视图将是你需要掌握的最重要的概念。

流数据库——物化视图在前几章中,我们只简要提到了物化视图。在真正理解流数据库之前,物化视图将是你需要掌握的最重要的概念。

但是,有没有一种方法可以让我们同时拥有高灵活性和低延迟,而无需两个 SQL 查询呢?我们可以通过使用向 WAL(写前日志)发出更改的物化视图来实现这一点。客户体验如下:

  1. 客户端提交一个推查询。此查询创建一个物化视图。
  2. 然后,客户端订阅物化视图中的更改,就像订阅 WAL 一样。

通过这种方法,客户端提交的是一个推查询,而不是拉查询。通过允许客户端对推查询进行更改,您可以获得执行临时查询所需的灵活性。此外,通过订阅物化视图的更改,查询延迟不再是问题,因为增量更改会在到达时推送给客户端。这意味着客户端不再需要发起拉查询并等待结果,从而降低了延迟。客户端只需要一个 SQL 查询即可开始接收实时分析数据。

今天,这种模式很难实现,因为推查询和拉查询通常在不同的系统中执行。推查询通常在流处理器中执行,而拉查询则在为终端用户服务的 OLAP 系统中执行。此外,推查询和拉查询通常由不同的工程团队编写。流数据工程师会编写推查询,而分析师或开发面向用户应用程序的开发人员会调用拉查询。

要摆脱这种困境,您需要一个系统具备以下功能:

  • 流处理能力,例如构建物化视图
  • 能够将物化视图公开到流平台中的主题,类似于 WAL
  • 能够以优化的方式存储数据以服务于数据需求
  • 能够提供同步和异步的服务方法

这些功能目前仅在流数据库中可用。流数据库能够将流处理平台和数据库结合在一起,并使用相同的 SQL 引擎处理运动中的数据和静止的数据。我们将在第 5 章中详细讨论这一点。

最常见的实时分析解决方案是运行像 Apache Flink 这样的流处理平台和像 Apache Pinot 这样的实时 OLAP 数据存储(见图 4-9)。

流数据库——物化视图在前几章中,我们只简要提到了物化视图。在真正理解流数据库之前,物化视图将是你需要掌握的最重要的概念。

图 4-9 展示了 OLTP 数据库中的数据如何传输到 RTOLAP 系统并为客户端服务的路径。让我们更详细地了解一下这种架构:

  1. 实体按照领域驱动设计的原则在 OLTP 数据库中表示为表。
  2. 应用程序在表中插入、更新或删除记录。这些更改被记录在数据库的 WAL(写前日志)中。
  3. CDC(变更数据捕获)连接器读取 WAL,并将这些更改写入流平台中的主题。流平台通过将更改发布到模拟 WAL 结构的主题/分区中,将 OLTP WAL 外部化。消费者可以读取这些主题/分区,以从原始 OLTP 数据库中构建表的副本。
  4. 流处理器是读取这些主题并使用物化视图构建内部表副本的系统之一。当物化视图异步更新时,它会将更改输出到另一个主题中。
  5. RTOLAP 数据存储读取包含物化视图输出的主题,并对数据进行优化,以便用于分析查询。

在图 4-9 中,流处理器在步骤 4 执行推查询,而拉查询则在步骤 5 被调用。需要注意的是,每个查询在不同的系统中执行,并且由不同的工程师编写。

图 4-10 进一步展示了推查询和拉查询之间的复杂性和分工。推查询执行复杂的转换任务,并将结果存储在物化视图中。物化视图将其更改记录在本地存储中,并通过流平台中的主题将物化视图暴露给持有 RTOLAP 系统的服务层。

流数据库——物化视图在前几章中,我们只简要提到了物化视图。在真正理解流数据库之前,物化视图将是你需要掌握的最重要的概念。

因此,与 RTOLAP 系统交互的终端用户无法灵活定义预处理逻辑,从而使拉查询能够以低延迟运行(见图 4-11)。

流数据库——物化视图在前几章中,我们只简要提到了物化视图。在真正理解流数据库之前,物化视图将是你需要掌握的最重要的概念。

让编写拉查询的终端用户也提供流数据的优化逻辑将有助于避免这些情况。不幸的是,由于当前流架构的状态,这种情况经常发生。

当我们尝试将 CDC 数据直接复制到 RTOLAP 系统时,问题就更加严重了。

CDC 和 Upsert

术语“upsert”是“update”(更新)和“insert”(插入)的结合,用于描述应用程序在插入和/或更新数据库表时所采用的逻辑 。Upsert 描述了一种逻辑,涉及应用程序检查记录是否存在于数据库表中。如果通过搜索主键发现记录存在,则会调用更新语句;否则,如果记录不存在,应用程序会调用插入语句将记录添加到表中。

我们了解到,CDC 数据包含诸如插入、更新和删除等增量更改。Upsert 逻辑处理了 CDC 流中三种更改中的两种(稍后我们将回到删除更改的处理) 。

Upsert 操作可以间接提高选择查询的性能和准确性,尽管 Upsert 本身主要专注于数据修改,但它们可以通过维护数据完整性和优化数据存储对选择查询性能和准确性产生积极影响。以下是 Upsert 如何在这些方面做出贡献的:

  • 数据完整性和准确性 Upsert 通过防止重复记录并确保数据的准确性和一致性来维护数据完整性。当选择查询从使用了适当 Upsert 操作的数据库中检索数据时,它们更有可能返回准确且可靠的信息。
  • 简化拉查询 从包含适当 Upsert 操作的表中选择数据可以简化查找时的查询。不必执行重复数据删除或筛选最新记录可以简化 SQL 并减少其执行的延迟。

Upsert 操作像推送查询一样有助于优化和简化拉查询。这是控制推送和拉查询之间平衡的因素之一。让我们通过图 4-12 中的 CDC 场景来更好地理解这一点。

流数据库——物化视图在前几章中,我们只简要提到了物化视图。在真正理解流数据库之前,物化视图将是你需要掌握的最重要的概念。

事务从应用程序发送到 OLTP 数据库中的表,执行插入、更新或删除操作。假设使用场景是更新绿色 T 恤的库存,因此涉及的表是 Products 表。

更新操作被写入 OLTP 数据库的 WAL(预写日志)中。

假设正在读取 WAL 的连接器刚刚启动,这需要连接器获取 Products 表的当前快照,以了解其当前状态。

如果连接器没有这个快照,下游系统就无法构建 Products 表的准确镜像副本。

通过获取表的快照,连接器创建了逻辑上等同于为 Products 表中每条记录插入的种子事件。

一旦此快照在主题中可用,我们就可以构建表的副本。仅凭增量更改是无法构建副本的。

当流处理器启动时,如果它是第一次消费该主题,它将从头开始读取。否则,它将从存储的偏移量开始读取。从头读取主题允许流处理器构建 Products 表的副本。同样,仅凭增量更改是无法构建表副本的。

复杂的转换操作在流处理器中实现。这些操作要求流处理器构建一个代表 Products 表副本的物化视图。

转换操作在物化视图等表格结构上或之间进行。如果不需要转换,则无需创建物化视图,数据流可以直接从输入主题传递到输出主题。

输出主题类似于输入主题,它包含用于为任何下游副本提供种子的快照数据。然而,数据已经在流处理器内经过了转换。对于 CDC 数据,此管道中的主题内容需要能够为下游副本提供种子。

如果 RTOLAP 数据存储直接从主题中读取,它将需要自己处理 upsert 逻辑。为此,它还需要理解主题中的数据,以识别插入、更新和删除操作,并将其应用到现有的内部表中。

这是步骤 6 的替代方案。在这种情况下,流处理器将数据直接发送到 RTOLAP 数据存储。对于不支持 upsert 的 RTOLAP,流处理器将必须执行 upsert 逻辑,而不是由 RTOLAP 系统处理。

由于 upsert 操作定义上只支持插入和更新,删除通常被忽略。一些系统会将 upsert 实现为也包含删除逻辑。其他系统,如 Apache Pinot,只会标记已删除的记录,以便恢复其先前版本。在这些情况下,使用 RTOLAP 的 upsert 实现非常重要,这需要 RTOLAP 直接从输出主题中读取。一些 RTOLAP 可能不公开删除功能,因此这项工作必须在流处理器中完成。

警告:

步骤 3 讨论了将 Product 表的快照保存在主题中。在第 1 章中,我们谈到了主题有一个保留期,过期的记录会被截断。对于 CDC 数据,需要一种不同类型的主题,称为压缩主题,在这种主题中,截断过程保留每个主键的最新记录。这允许保留旧数据,从而实现下游表副本的物化,包括历史记录。

总结:

upsert 逻辑可以在 RTOLAP 系统或流处理器中实现。更简单且更可取的方法是让 RTOLAP 从输出主题中读取并自己应用 upsert 逻辑。输出主题还提供了一个缓冲区,以防流处理器生产数据的速度快于 RTOLAP 的消费速度。

upsert 突显了两个实时系统在复杂逻辑的所有权问题上争斗或回避的痛苦。这些痛苦将进一步加剧数据工程师和分析终端用户之间的冲突。

在流处理中理解 CDC 可能很困难,因为它涉及许多结构和复杂逻辑。例如,它与 OLTP 数据库中的 WAL 相关联,它需要在流平台中使用压缩主题来保留历史记录,它需要 upsert 来简化和加快拉查询,它还需要在视图中物化。当多个系统介入从最初的 OLTP 源到 RTOLAP 数据存储的过程中,仅仅为了构建 Products 表的副本时,这种困难会进一步加剧。正如我们所指出的,有一些方法可以整合这些系统,减少冗余和复杂性。流数据库是一种实现这种整合的方式。

包括丰富内容的转换将需要在流处理器中加入多个流。回想一下两种类型的流:变更流和仅追加流。变更流包含业务域中实体的变更数据,如产品和客户。仅追加流包含事件,如来自应用程序的点击流数据。让我们再次通过流数据管道,看看如何实现这一点。

合并流(Joining Streams)

如前所述,转换操作是在包含变更流(materialized views)和仅追加流(append-only streams)的表格结构上或之间进行的。仅追加流类似于变更流,唯一允许的更改是插入。实际上,你可以认为数据库中的所有表格结构都是数据流的序列,数据进入和离开表格结构。

将仅追加流表示为物化视图的主要原因之一是物化视图需要存储结果。由于仅追加流只是插入且不断增长,最终你会耗尽存储空间,就像你不会将点击事件写入数据库一样,因为它也会耗尽存储空间。

由于变更流和仅追加流都表示为表格结构,不同的流处理系统可能会给这些结构起不同的名字。在本书中,我们将使用以下术语来描述流处理器中的表格:

  • 追加表(Append tables) :一种包含仅追加流的表格结构。这些结构没有状态存储支持,代表了流经流处理器的数据。
  • 变更表(Change tables) :一种表示物化视图的表格结构,支持状态存储。

我们还需要以类似方式区分流平台中的主题。了解主题中的数据类型将指示如何处理或表示表格结构。我们使用以下术语来识别流平台中的主题:

  • 追加主题(Append topics) :包含仅追加数据的主题。
  • 变更主题(Change topics) :包含变更事件或 CDC(变更数据捕获)事件的主题。一些 Kafka 工程师也称这些为“表主题(table topics)”。

使用这些术语,我们可以更好地描述流如何合并在一起,因为逻辑可能会变得混乱。使用 SQL 作为定义合并和转换的语言非常重要,因为 SQL 是操作数据的通用语言,SQL 引擎需要结合流和数据库。共享一个 SQL 引擎来操作流动中的数据和静态数据,最终会导致出现流数据库。

Apache Calcite

让我们从第 2 章中描述的追加表和变更表的合并开始。示例 4-1 中的 SQL 基于 Apache Calcite,这是一个用于构建基于关系代数的数据库的数据管理框架。关系代数是一种正式的数学方法,用于描述可以在关系数据库上执行的操作。它是一组帮助我们操作和查询存储在表格(也称为关系)中的数据的规则和符号。

Apache Calcite 包含了许多构建数学运算的组件,但省略了一些关键功能:数据存储、数据处理算法以及存储元数据的存储库。如果你想从头开始构建一个数据库,Apache Calcite 是其中一个构建块。事实上,许多现有的实时系统都使用了 Calcite,例如 Apache Flink、Apache Pinot、Apache Kylin、Apache Druid、Apache Beam 和 Apache Hive 等。

Calcite 有意避免涉足数据存储和处理的业务……这使其成为在应用程序与一个或多个数据存储位置和数据处理引擎之间进行中介的绝佳选择。它也是构建数据库的理想基础:只需添加数据。

——Apache Calcite 文档

这就是我们要做的——只需添加数据。我们回到点击流的使用场景,其中我们有三个数据源,每个数据源都有自己的主题在流平台中。

示例 4-1. 合并到表主题

CREATE SINK clickstream_enriched AS
SELECT
  E.*,
  C.*,
  P.*
FROM CLICK_EVENTS E 
JOIN CUSTOMERS C ON C.ip=E.ip 
JOIN PRODUCTS P ON P.product_id=E.product_id 
WITH (
   connector='kafka',
   topic='click_customer_product',
   properties.bootstrap.server='kafka:9092',
   type='upsert',
   primary_key='id'
);
  • CLICK_EVENTS 是从追加主题中获取的追加表,包含来自用户界面应用程序的点击事件。
  • CUSTOMERS 是从变更主题中获取的变更表,包含使用 CDC 连接器捕获的 OLTP 数据库中的变更事件。
  • PRODUCTS 是从变更主题中获取的变更表,也包含通过 CDC 连接器从 OLTP 数据库捕获的变更事件。这里我们假设产品 ID 值是从点击 URL 中提取并放入一个单独的列 product_id 中。

只要支持 SQL,流处理平台就可以将主题中的数据表示为表格结构,因此可以使用 SQL 和 Calcite 等工具来定义复杂的转换。示例 4-1 是一个内连接(inner-join),将 CLICK_EVENTS、CUSTOMERS 和 PRODUCTS 三个表中存在的匹配记录连接在一起。

任何汇总或合并流的流 SQL 输出都是物化视图。在这种情况下,我们正在合并:

  • CLICK_EVENTS:一个包含点击事件的追加表
  • CUSTOMERS:一个所有客户的变更表/物化视图
  • PRODUCTS:另一个产品的变更表/物化视图

不同类型的表连接有以下属性:

  • 追加表与追加表的连接:这始终是有窗口的,否则状态存储会耗尽空间。
  • 变更表与变更表的连接:不需要窗口,因为连接结果可以适当存储在状态存储中。
  • 变更表与追加表的连接:这也是有窗口的,否则状态存储会耗尽空间。

请注意,任何时候仅追加流是连接的一部分时,都需要使用窗口来限制状态存储中保存的数据。

在使用 SQL 进行流处理时,当你对流执行左连接操作(append 表和变更表之间的流)时,结果由追加表驱动。SQL 中的这种连接如下所示:

SELECT ...
FROM append_table_stream
LEFT JOIN change_table_stream ON join_condition;

在此,append_table_stream 和 change_table_stream 代表你要连接的两个输入流,join_condition 指定了如何匹配两个流的条件。

左流(append_table_stream)首先在 FROM 子句中指定,驱动连接的结果。结果将包含左流中的所有事件,对于左流中的每个事件,它将包括基于 join_condition 从右流(change_table_stream)中匹配的事件。

我们以点击流示例中的两个流为例:点击流和客户流。点击流中的每个事件代表一个包含客户 ID 的点击,而客户流中的每个事件代表一个包含客户 ID 的客户。要在客户 ID 上连接两个流,你可以编写如下 SQL 查询:

SELECT k.product_id, c.customer_name
FROM click k
LEFT JOIN customers c ON k.customer_id = c.customer_id;

在这个例子中,点击流是左流,它驱动连接的结果。对于点击流中的每个客户事件,查询会根据匹配的客户 ID 从客户流中获取相应的客户姓名。

请注意,在流处理过程中,连接是连续和动态的。随着新事件到达输入流,连接结果会被持续更新并作为结果流发出。这使得你能够使用 SQL 对流数据进行实时处理和分析。

点击流使用场景

让我们退后一步,逐步清楚地了解图 4-13 中的完整示意图。

流数据库——物化视图在前几章中,我们只简要提到了物化视图。在真正理解流数据库之前,物化视图将是你需要掌握的最重要的概念。

  1. 客户信息被保存到 OLTP 数据库中。

  2. 在 OLTP 数据库上运行 CDC(变更数据捕获)过程,捕获对 CUSTOMERS 表的更改,并将其写入 CDC 主题。该主题是一个压缩主题,可以被视为 CUSTOMERS 表的副本。这使得其他系统可以构建 CUSTOMERS 表的副本。

  3. 同一位客户在电子商务应用程序上点击了一个产品。

  4. 点击事件被写入一个主题。我们不将点击事件写入 OLTP 数据库,因为点击事件只是插入操作。如果将其捕获到 OLTP 数据库中,最终可能导致数据库存储空间不足。

  5. 流处理器从 CDC 和点击事件主题中读取数据。

  6. 这些是来自流处理器中 CUSTOMERS 变更表主题的消息。它们存储在状态存储中,其大小取决于窗口大小(或者在例如 Kafka Streams 或 ksqlDB 中完全存储在 KTable 中)。

  7. 这些是来自流处理器中 CLICK_EVENTS 追加表主题的消息。

  8. 在 CLICK_EVENTS 追加表消息和 CUSTOMERS 变更表消息之间执行左连接。连接的结果是用相应的客户信息(如果存在)丰富的 CLICK_EVENTS。

  9. 流处理器将其输出写入以下主题。

    • 这是一个变更主题,包含 CDC CUSTOMER 变更。这是一个冗余主题,因为步骤 1b 中的主题包含相同的数据。我们保留它以保持图表的平衡。
    • 这是一个追加主题,包含原始 CLICK_EVENT 数据和丰富的客户数据。
  10. 主题被拉入 RTOLAP 数据存储中以进行实时服务。

  • 这是从变更主题构建的 OLTP 数据库中原始 CUSTOMERS 表的副本。
  • 这包含丰富的 CLICK_EVENTS 数据。
  1. 用户对 RTOLAP 数据存储发出查询。
  • 用户可以直接查询 CUSTOMERS 表。
  • 用户可以查询丰富的 CLICK_EVENTS 数据,而无需自己进行数据连接,因为连接已经在流处理器中完成。

正如我们之前所指出的,你可以选择在流处理器中实现连接,也可以由用户来实现。在这种情况下,我们决定预先连接 CLICK_EVENTS 和 CUSTOMER 数据,以提高用户查询性能。连接的繁重工作由流处理器完成,使 RTOLAP 能够专注于快速、低延迟的查询。在这种场景中,流处理器创建了一个物化视图,并将其写入 5b 步骤中的主题。RTOLAP 从 5b 步骤中的主题在其内部构建物化视图的副本。在 RTOLAP 数据库中,我们可能需要实现一个保留方案,以删除较旧的丰富 CLICK_EVENTS,以避免存储空间不足。

或者,我们可以直接绕过流处理器,让 RTOLAP 在用户发出查询时执行连接。这将不需要构建物化视图,也可以避免管理另一个复杂的流处理系统的需求。但是,这样的查询会很慢,并且会给 RTOLAP 系统带来很大的压力。

那么,如何在减少架构复杂性的同时,仍然获得物化视图的性能呢?这就是我们通过使用流数据库,将流处理与实时数据库结合起来的地方。

总结

Mihai Budiu 在 2023 年 3 月的演讲“使用 Calcite 构建流增量视图维护引擎”中大胆地宣称:“我将做一个非常大胆的声明,你们迄今为止看到的所有数据库实际上都是流数据库。”

传统上,流处理系统和数据库被视为两个独立的实体,流处理系统负责处理实时、持续流动的数据,而数据库则管理持久化的、可查询的数据。然而,物化视图通过在这两种系统之间架起桥梁,挑战了这种分离。

物化视图使得可以从流数据源创建预计算的、持久化的数据摘要。这些视图作为缓存,存储了计算结果或聚合数据,使其易于查询。这意味着,我们不仅可以依赖流处理系统进行实时分析,还可以利用物化视图存储和查询总结后的数据,而无需连续地重新处理整个数据集。

通过结合流处理和数据库的优势,物化视图提供了多个优点。首先,它们可以更高效、更具可扩展性地对流数据执行复杂分析。与每次查询都重新处理整个数据集不同,物化视图存储了预计算的结果,允许更快速、更响应的查询。

此外,物化视图促进了流处理与批处理范式的无缝集成。它们可以用于存储流处理管道的中间结果,作为流数据的连续流动和通常在数据库上执行的批量分析之间的桥梁。这种集成有助于统一处理模型,并简化数据密集型系统的整体架构。

总的来说,物化视图模糊了流处理和数据库之间的界限,使我们能够利用持久的、可查询的流数据摘要。通过结合两者的优点,物化视图实现了高效且可扩展的实时分析,历史数据和实时数据的无缝集成,以及流处理和批处理范式的融合。使用物化视图为构建智能且响应迅速的数据系统打开了新的可能性,这些系统不仅可以处理流数据的动态性,还能够提供快速灵活的查询能力。

我们现在引入了 OLTP 数据库中的两个构造,使它们更接近流技术:

  • WAL:捕获数据库表变更的构造。
  • 物化视图:一个异步查询,它预处理和存储数据以实现低延迟查询。

在第一章中,我们引用了 Martin Kleppmann 的一句话:“将数据库颠倒过来”。实际上,我们确实将数据库“颠倒”了:

  1. 将 OLTP 中的 WAL 构造发布到流平台(如 Kafka)。
  2. 在有状态的流处理平台中模拟物化视图功能,从而无需 OLTP 数据库执行复杂的转换,使它们能够专注于捕获事务和提供数据,并将这些任务外部化到流层。

我们现在已经为讨论下一章的流数据库奠定了基础。在下一章中,我们将再次逆转流处理范式,将 WAL 和物化视图重新引入数据库。换句话说,我们将“将流架构从外到内重新构建”。

转载自:https://juejin.cn/post/7402534719776620585
评论
请登录