likes
comments
collection
share

流数据库简介在电子表格中,你可以在一个单元格中输入一个公式(例如,另一列中单元格的总和),并且当公式中的任何输入发生变化

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

在电子表格中,你可以在一个单元格中输入一个公式(例如,另一列中单元格的总和),并且当公式中的任何输入发生变化时,公式的结果会自动重新计算。这正是我们在数据系统层面想要实现的效果:当数据库中的一条记录发生变化时,我们希望与该记录相关的任何索引能够自动更新,并且依赖于该记录的任何缓存视图或聚合能够自动刷新。你不需要担心这种刷新是如何发生的,只需要信任它能正确地工作。—— 马丁·克莱普曼,《设计数据密集型应用》

在上一章中,我们学习了如何“将数据库反转”,正如马丁·克莱普曼恰当地描述的那样。这涉及将数据库的WAL(写前日志)外部化为输入更改流,在其上创建物化视图,并将处理后的数据写回到输出更改流中。不像经典数据库(如Oracle或Postgres)中的物化视图,它们的刷新间隔从几分钟到几小时不等,而在流处理平台如Flink、Kafka Streams、ksqlDB或Samza中,物化视图可以随着每一个新更改的到来而持续刷新。

“将数据库反转”这一想法使我们能够构建比以往更为实时的数据物化视图。然而,与简单的经典数据库安装相比,这也带来了大量额外的复杂性:为了实际利用由Flink、Kafka Streams、ksqlDB或Samza创建的这些持续更新的物化视图,输出更改流必须被引入到另一个外部数据库中(例如,像Druid、Pinot、ClickHouse或Rockset这样的实时在线分析处理数据库)。因此,从架构上讲,“将数据库反转”迫使我们运行和管理三个系统(流平台、流处理器和外部数据库),而不是仅仅拥有一个经典数据库。而且,更复杂的是,只有昂贵且难以找到的流处理专家才能实现这一点,而不是普通的数据库专家。因此,从整体来看,“将数据库反转”这一想法仅在一些低延迟至关重要的用例中被应用,比如欺诈检测。

我们相信,“将数据库反转”的想法是构建流处理与数据库世界之间桥梁的重要第一步,它为流处理的重大进步铺平了道路。但它并没有完全解决所有问题。

在本书的这一核心章节中,我们将带领你从有状态的流处理、基于流的物化视图和状态存储,迈向基于它们最初在数据库世界中定义的新型物化视图。我们将向你展示,像ksqlDB、Materialize、RisingWave和Timeplus这样的新兴流数据库,如何开始在流处理和数据库世界之间搭建桥梁,并在“将数据库反转”之后采取下一个逻辑步骤:将其“反转回来”。要开始这个旅程,让我们回顾一下你已经熟悉的那些组件。

识别流数据库

到目前为止提供的图示主要包含以下组件,这些组件也显示在图5-1中。现在,让我们忽略从系统中读取和写入所需的连接器。

流数据库简介在电子表格中,你可以在一个单元格中输入一个公式(例如,另一列中单元格的总和),并且当公式中的任何输入发生变化

从左到右,图5-1中的组件如下:

  1. 数据库:可以是我们之前讨论的三种数据存储类型之一:OLTP、RTOLAP,以及流处理器中的内部状态存储。它们之间的区别决定了数据的存储和查询方式。
  2. 主题(Topic) :是模仿OLTP数据库中WAL(预写日志)的一种结构。主题发布数据流到其他数据库和流处理器。
  3. 流处理器:是用于转换数据流的应用程序。它们持有一个内部状态存储。
  4. 物化视图(Materialized View) :是预计算结果并将其存储在数据库中的一个过程。物化视图可以在数据库或流处理器中创建,二者都需要有持久化层。

我们可以排列这些组件,以构建一个表示数据流的流拓扑。到目前为止,我们建议的数据流如下所示:

示例描述
Database → Topic → Stream Processor → Topic → Database简单流

到目前为止,我们一直关注以下的流动,这是我们用于点击事件用例的典型实时分析流:

示例描述
OLTP → Topic → Stream Processor → Topic → RTOLAP从OLTP数据库流向RTOLAP
Microservice → Topic → Stream Processor → Topic → RTOLAP捕获点击事件的微服务流向同一RTOLAP

但是没有什么能阻止我们执行以下操作,其中输出是另一个或甚至相同的OLTP数据库。这一重要区别将在本章稍后讨论不同类型的流数据库和物化视图时显现出来:

示例描述
OLTP → Topic → Stream Processor → Topic → OLTP目标是另一个或相同的OLTP数据库的流

在所有这些流中,物化视图的确切位置是模糊的。流的结果在以下三个组件中物化:

  1. 流处理器创建物化视图并将结果存储在其内部状态存储中。
  2. 物化视图的更改被推送到输出主题。
  3. 流处理器的内部状态存储或输出主题都不能直接查询。因此,目标数据库将更改拉入自己的物化视图,这与表对应。该表针对终端用户的分析查询进行了优化。

我们唯一确定的是,物化视图不是一个一等公民,因为它跨越了图5-2中间的所有三个组件。

流数据库简介在电子表格中,你可以在一个单元格中输入一个公式(例如,另一列中单元格的总和),并且当公式中的任何输入发生变化

如果我们能够整合这三个组件会怎么样?这就是流数据库的一个例子。如果我们将流处理器和数据库融合在一起,那么我们就不再需要通过主题来公开流处理器状态存储中的物化视图的更改。相反,流处理器的状态存储和数据库将合二为一(参见图5-3)。

流数据库简介在电子表格中,你可以在一个单元格中输入一个公式(例如,另一列中单元格的总和),并且当公式中的任何输入发生变化

这个流数据库将能够处理推送查询和拉取查询。如果你还记得,推送查询是一个像流处理器一样在后台异步运行的过程,而拉取查询是由终端用户请求的分析查询。这意味着同一个SQL引擎需要同时支持推送和拉取查询。

将存储整合到一个地方只能解决问题的一半。构建一个同时支持静态数据和动态数据的单一SQL引擎更为困难。在进行数据联结和聚合时,流数据库的流处理部分可能需要从使用其内部状态存储切换到使用真正的数据库。此外,流数据库中的对象可能需要区分动态数据表和静态数据表。

基于列的流数据库

回到我们的点击流用例,流处理管道的目标是一个RTOLAP数据存储。让我们更新一下这个图表,以便在图5-4中使用一个(列式)流数据库。

流数据库简介在电子表格中,你可以在一个单元格中输入一个公式(例如,另一列中单元格的总和),并且当公式中的任何输入发生变化

这种解决方案为流数据库用户提供了一种在同一位置使用SQL编写推送查询和拉取查询的方式。推送查询可以是一个SQL查询,用于创建一个物化视图,就像示例5-1中所展示的那样。

示例 5-1. 从两个数据流创建物化视图

create materialized view CUSTOMER_CLICKS
as select * from CLICK_EVENTS E
join CUSTOMERS C on C.ip = E.ip

应用程序还可以调用一个拉取查询,如示例5-2中所示。

示例 5-2. 应用程序调用返回客户详细信息的拉取查询

select * from CLICK_EVENTS E
join CUSTOMERS C on C.ip = E.ip
where C.id = '123'

示例5-1由以前的流处理引擎调用,示例5-2则由以前的RTOLAP SQL引擎调用以实现低延迟执行。

基于行的流数据库

图5-5显示了一个基于行的流数据库甚至可以成为流数据管道的目标。如果你还记得这些差异,基于行的数据库,如OLTP数据库,优化了与事务性数据相关的快速读写操作,如CRUD(创建、检索、更新、删除)事务。而OLAP则优化了聚合等分析查询。那么,如果用例是对其进行分析查询,为什么还会将基于行的数据库作为目标呢?

警告

一般不推荐使用OLTP数据库来处理分析工作负载,原因包括性能问题、锁定与并发问题、资源利用等多个方面。

前一段中提出的问题的答案是:你不必在基于行的流数据库上运行分析查询。你仍然可以选择将其输出发布到一个输出主题,并随后将其馈送到一个RTOLAP数据库以进行分析查询,如图5-5右侧所示。但通过整合系统,你并没有减少复杂性。你只是用另一个流处理器替换了之前的流处理器——这次是一个基于行的流数据库,而且你的物化视图再次跨越多个流系统:基于行的流数据库、主题和RTOLAP。

流数据库简介在电子表格中,你可以在一个单元格中输入一个公式(例如,另一列中单元格的总和),并且当公式中的任何输入发生变化

从本质上讲,流处理是一个逐行传输数据的过程,更适合与基于行的流数据库一起工作。对于基于行的流数据库,更好的解决方案是通过一种称为命令查询职责分离(CQRS)的模式将转换后的数据保留在应用程序内。

CQRS 是一种架构模式,它将读取数据的操作(查询)与修改数据的操作(命令)分离成独立的组件。在CQRS 中,想法是为读取和写入数据分别创建独立的模型。这种分离允许优化性能和可扩展性,因为读取和写入操作可以根据各自的特定需求独立优化。CQRS 有助于提高系统响应能力、增强可扩展性,并使数据模型更好地与其预期用途对齐。

流数据库简介在电子表格中,你可以在一个单元格中输入一个公式(例如,另一列中单元格的总和),并且当公式中的任何输入发生变化

在图5-6中,输出主题中的数据被写回到源OLTP数据库中。更好的解决方案是将输出主题中的数据更新到一个单独的数据库,该数据库仅供客户端应用程序读取。该模式在写入和读取OLTP数据库之间提供了最终一致性。此外,您甚至可以使用流数据库作为应用程序可访问的只读数据库,在CQRS模式中使用(见图5-7)。

流数据库简介在电子表格中,你可以在一个单元格中输入一个公式(例如,另一列中单元格的总和),并且当公式中的任何输入发生变化

基于行的流数据库将流处理带得更接近边缘,或称为网络边缘。

注意

术语“网络边缘”通常指的是Web应用程序或服务的最外层或边界。它代表了应用程序与外部世界(包括用户、客户端和其他系统)之间的交互点。网络边缘负责处理传入的请求、对其进行处理,并将它们路由到应用程序架构内的适当组件。

边缘流式数据库

到目前为止,我们已经将流处理器、主题和目标数据库整合到一个流数据库中,如图5-8右侧所示,这就是基于列的流数据库。

流数据库简介在电子表格中,你可以在一个单元格中输入一个公式(例如,另一列中单元格的总和),并且当公式中的任何输入发生变化

新兴的数据库开始出现,如图中左侧所示,它们更接近于应用程序和网络边缘。我们将在第7章中讨论这些数据库,特别是混合事务和分析处理数据库(HTAP数据库)。

到目前为止,我们详细讨论了两种类型的流数据库:基于行的和基于列的。这两者都面临着将SQL引擎在流处理器和目标数据库中融合的艰巨任务。这两个SQL引擎可能具有不同的语义,这可能会限制SQL中可以表达的内容,因为底层的融合SQL引擎不支持这些内容。

SQL Expressivity

SQL 表达能力(SQL Expressivity)指的是 SQL 引擎使用简洁的语法有效表达复杂数据操作和查询的能力。换句话说,它衡量了 SQL 在捕捉查询或操作意图时的易用性和可维护性。在流处理器和 OLAP 或 OLTP 数据库之间合并 SQL 引擎可能会引发各种挑战和问题,原因在于它们在设计、使用场景和性能特性上的根本差异:

性能不匹配

流处理器针对高速度的实时数据流进行了优化,而 OLAP 数据库则设计用于处理历史数据的复杂分析查询。合并它们的 SQL 引擎可能导致性能问题,特别是在合并后的引擎难以平衡流数据的实时需求与 OLAP 查询的资源密集型特性时。

延迟

流处理要求低延迟的实时处理,而 OLAP 数据库通常优先考虑查询优化,而非低延迟响应。在单个 SQL 引擎中同时实现流处理的低延迟和分析查询的高性能是具有挑战性的。

资源分配

流处理器和 OLAP 数据库的资源需求不同。流处理器需要实时处理数据,这可能会与 OLAP 查询争夺大量计算和内存资源。正确分配资源至关重要,以避免瓶颈。

数据建模差异

流处理器通常处理原始或半结构化数据,而 OLAP 数据库需要结构化、预处理和良好建模的数据以进行高效查询。合并这两个 SQL 引擎可能会导致数据建模方法上的冲突。

数据一致性

流处理器通常处理运动中的数据,而 OLAP 数据库则处理静态数据。在处理数据的一致性时,合并 SQL 引擎可能会增加复杂性,尤其是在同时处理这两种状态的数据时。

复杂性

结合流处理和 OLAP 数据库的能力可能会导致系统更复杂。这种复杂性会影响系统的可维护性、调试以及整体稳定性。

数据量和保留期

流处理器由于数据量大和实时处理要求,可能有较短的数据保留期。OLAP 数据库通常会存储历史数据以进行更长时间的分析。如何处理数据保留和集成可能是一个挑战。

查询优化

OLAP 数据库通常提供复杂分析查询的高级查询优化技术。流处理器可能无法提供同样水平的优化,可能导致 OLAP 查询的性能不佳。

模式演变

流处理器在处理模式演变方面可能比 OLAP 数据库更加灵活,后者通常需要定义良好的模式。合并 SQL 引擎可能在处理数据模式演变时遇到困难。

维护和更新

管理一个处理流处理和 OLAP 工作负载的综合 SQL 引擎的更新和维护会更加困难,因为更新必须考虑这两种使用场景的需求。

为了减轻这些问题,必须进行仔细的架构规划、全面的性能测试,并深入了解每个 SQL 引擎的具体使用场景。

相比于合并 OLAP 和流处理器之间的 SQL 引擎,合并 OLTP 和流处理器之间的 SQL 引擎可能会更容易一些。这是由于 OLTP 和 OLAP 系统在数据存储和处理特性上的根本差异:

数据格式

OLTP 数据库通常使用基于行的存储模型,这非常适合捕捉单个事务记录。流处理器也处理基于行的数据格式,因为它们处理实时事件。这种数据模型上的一致性有助于更顺利的集成和兼容性。

实时特性

OLTP 系统和流处理器都在一定程度上处理实时数据。虽然处理要求可能不同,但对实时数据处理的共同关注可以使合并它们的 SQL 引擎变得更容易。

事务处理

OLTP 和流处理器都涉及到一定复杂度的事务处理。这一共同点在处理数据一致性和更新时可以促进更好的集成。

事件驱动

流处理器本质上是事件驱动的,这与 OLTP 数据库中的实时更新相吻合。这种兼容性简化了集成过程。

尽管在 OLTP 和流处理器之间合并 SQL 引擎可能更容易,但在 OLAP 和流处理器之间合并 SQL 引擎要复杂得多,因为它们在数据存储、处理和查询优化策略上存在根本性差异。无论是哪种情况,成功实现集成都需要仔细的架构规划和对每个系统具体要求的考虑。

减少基础设施的复杂性和统一的 SQL 引擎将使工程师的开发工作更容易,尤其是在调试物化视图时。

流处理调试能力

数据工程师在编写数据管道时,总是需要验证其 SQL 逻辑。如果物化视图分布在三个不同的分布式系统(流处理器、主题、OLAP)之间,这将使调试变得非常困难。虽然理论上可以仅通过查看输入和输出主题来进行调试,但实际上,调试需要同时查看这些主题和外部数据库。

流数据库通过提供比传统流处理器的键/值状态存储更高级的物化视图,使调试变得更容易。流数据库将物化视图保存在基于行或基于列的存储中,从而使在一个地方验证结果变得更加容易。由于数据已经被索引,可以更快地执行复杂的临时查询,从而加速调试过程。相反,像 Flink 这样的流处理器首先需要将结果写入数据库,以通过临时查询测试其有效性。

流数据库调试的优势

以下是流数据库调试的一些优势:

  1. 熟悉的 SQL 接口:许多流数据库提供类似 SQL 的查询语言来定义流处理操作。如果您已经熟悉 SQL,那么调试过程会因为语言的熟悉性而变得更加简单。
  2. 更简单的逻辑:流数据库通常提供更高级的抽象,这简化了复杂的流处理任务。这可以导致逻辑更简单,从而使调试更加容易。
  3. 集成的生态系统:流数据库通常是更大数据生态系统的一部分。与将流处理器和下游数据库分开使用相比,将流处理器和数据库集成在一个系统中,可以更好地实现与其他数据工具和监控解决方案的集成。这个集成的环境可以通过提供数据管道的整体视图来帮助调试。
  4. 内置优化:流数据库可能对常见的流处理模式进行了内置优化。这些优化可以帮助提高性能和可靠性,并减少在某些场景中进行复杂调试的需求。
  5. 更容易部署:一些流数据库设计为易于部署,这可以通过减少与部署相关的潜在问题来简化调试过程。

SQL 不是万能的

SQL 是一种非常抽象的语言。在某些用例中,除了 SQL 之外,能够使用较低级别的 DSL(领域特定语言)可能更具优势,例如 Kafka Streams(Streams DSL 和 Processor API)和 Flink(DataStream API)可以提供更高的表达能力。基于 SQL 的流数据库中的用户自定义函数(UDF)可以在一定程度上缓解这一问题,但仍有局限。

在调试和/或分析,特别是在高度性能关键的情况下,观察流处理系统推导出的实际逻辑执行计划也是非常有价值的。例如,检查由 GROUP BY 语句创建的聚合操作符的状态。然而,流处理系统用于检查执行计划的工具仍处于起步阶段。

流数据库的实现

表 5-1 列出了一些在本书发布时可用的流数据库。

表 5-1. 现有的流数据库

名称许可证状态存储实现用例
ksqlDBConfluent 社区许可证基于 RocksDB(LSM 树键值存储)CQRS,推送查询
RisingWaveApache 2基于行存储CQRS,推送查询,单行查找
Materialize商业源代码许可证(BSL)基于行存储CQRS,推送查询,单行查找
Timeplus(Proton)Apache 2基于列存储分析推送和拉取查询

表 5-1 中列出的流数据库在其底层持久化层方面存在差异。第一个流数据库 ksqlDB 使用基于 RocksDB(LSM 树存储)的状态存储。ksqlDB 仅支持按主键索引。虽然主键/值访问确实可以带来良好的性能,但更复杂的查询需要通过整个状态存储进行全扫描,这在处理非平凡的数据量时无法扩展。

全扫描,也称为表扫描,是一种查询操作,涉及检查和处理数据集、表或数据库中的每条记录或行。虽然全扫描在某些场景中很有用,但在查询数据时通常会引入各种问题和挑战。全扫描可能会占用大量资源并耗费时间,尤其是在处理大数据集时。处理表中的每一条记录可能会导致查询性能变慢,特别是在数据集非常大的情况下。

流数据库的持久化层实现决定了它可以有效支持的查询范围。RisingWave、Materialize 和 Timeplus 使用了更类似于数据库的持久化层实现(例如,Timeplus 使用了 RTOLAP 数据库 ClickHouse 的一个版本),并采用灵活的索引方案。这使得它们能够高效地处理多种拉取查询。

流数据库架构

在流数据库中,物化视图(即更改表)存储在状态存储中。相反,追加表则不存储在其中。追加表要么是直接通过清理,要么是被放置到流数据库中的另一种类似主题的构造中。

为了更好地总结流数据库的架构,图 5-9 描述了点击事件和客户 CDC 数据所遵循的路径和步骤。

流数据库简介在电子表格中,你可以在一个单元格中输入一个公式(例如,另一列中单元格的总和),并且当公式中的任何输入发生变化

步骤 1:引入点击事件

在第一步中,点击事件位于类似 Kafka 的流平台中的一个追加主题中。为了将这些数据引入流数据库中,我们需要调用数据定义语言(DDL)来创建表。如示例 4-1 所示。

注意 DDL 是 SQL 的一个子集,用于描述如何创建、修改和删除数据库对象,如表。

在示例 5-3 中,我们在流数据库中创建了一个源表。这与第 2 章中提供的 SQL 示例相同。

示例 5-3:从 Kafka 主题为点击事件创建源表

CREATE SOURCE click_events (
  id integer,
  ts long, 
  url varchar, 
  ipAddress varchar, 
  sessionId varchar,
  referrer varchar,
  browser varchar
)
WITH (
   connector='kafka',
   topic='clicks',
   properties.bootstrap.server='kafka:9092',
   scan.startup.mode='earliest'
)
ROW FORMAT JSON;
  • ts: 时间戳
  • url: 包含需要解析的产品 ID
  • ipAddress: 识别客户的 IP 地址

接下来,我们使用示例 5-4 处理客户 CDC 数据。注意,在这一步中,数据作为一种“传递”形式存在,表现为一个没有状态存储的仅追加表。流处理器和流数据库无法判断主题中的内容是更改数据还是追加数据。它假设为追加数据,因为所有流数据在被物化为存储在状态存储中的视图之前都是追加数据。这就是为什么我们需要第 3 步。

示例 5-4:导入并创建客户表。表的架构使用 Debezium CDC 格式,其中包含 before、after 和 op 字段。

CREATE SOURCE customers (
    before ROW<id long, name varchar, email varchar, ipAddress varchar>, 
    after ROW<id long, name varchar, email varchar, ipAddress varchar>, 
    op varchar, 
    ts timestamp, 
    source <...>,
)
WITH (
   connector='kafka',
   topic='customers',
   properties.bootstrap.server='kafka:9092',
   scan.startup.mode='earliest'
)
ROW FORMAT JSON;
  • before: 记录更改之前的状态。
  • after: 记录更改之后的状态。
  • op: 标识更改的类型:插入、更新或删除。
  • ts: 更改发生的时间戳。

步骤 3:物化视图

在图 5-9 的第 3 步中,我们告诉流数据库将追加表中的值物化为一个物化视图。示例 5-5 展示了一种获取每条记录最新状态的方法。

示例 5-5:使用公共表表达式(CTE)和窗口功能创建包含最新记录的物化视图

CREATE MATERIALIZED VIEW customers_mv AS
WITH ranked_customers AS (
  SELECT
    c.AFTER,
    c.op,
    c.ts,
    ROW_NUMBER() OVER (PARTITION BY c.AFTER.id ORDER BY c.ts DESC) AS rn 
  FROM customers AS c
)
SELECT * FROM ranked_customers WHERE rn = 1 AND op IS NOT 'D'; 
  • 这是一个窗口语句,按 id 对记录进行分区,然后按时间戳降序排列。ROW_NUMBER() 为每个 id 的实例分配一个行号,并确保记录 1 是最新记录。
  • 我们仅选择最新记录,其 rn 值为 1。我们还过滤掉所有被删除的记录,即 op 不是 'D' 的记录。

示例 5-5 的输出应生成一个包含最新客户数据状态的物化视图。

某些流数据库能够通过使用自定义连接器来跳过第 3 步,直接将 Debezium CDC 格式的数据处理为物化视图(见示例 5-6)。

示例 5-6:使用 Debezium 连接器直接创建物化视图

CREATE SOURCE customers (
    before ROW<id long, name varchar, email varchar, ipAddress varchar>,
    after ROW<id long, name varchar, email varchar, ipAddress varchar>,
    op varchar,
    ts timestamp,
    source <...>,
)
WITH (
   connector='kafka-debezium-cdc', 
   topic='customers',
   properties.bootstrap.server='kafka:9092',
   scan.startup.mode='earliest'
)
ROW FORMAT JSON;
  • 自定义连接器 kafka-debezium-cdc 能够从 Kafka 主题中读取数据,并构建一个仅显示客户表最新状态的物化视图。

CDC 连接器 每个流数据库的自定义 CDC 连接器名称各不相同。不要期望其名称就是 kafka-debezium-cdc。需要参考文档以获取实际使用的连接器名称。

某些流数据库将有自己的 CDC 连接器,这些连接器会绕过流平台并直接连接到数据库。这些连接器仍将读取 WAL 并在流数据库中构建表的副本。然而,绕过流平台可能会限制 CDC 数据被复制到其他目标数据库的能力。我们将在第 10 章中详细讨论部署流数据库的架构选项。

步骤 4:创建另一个物化视图

在图 5-9 的第 4 步中,创建了另一个物化视图。示例 5-7CLICK_EVENTS 表与 CUSTOMERS 表连接,生成一个包含客户信息的丰富点击事件的物化视图。

示例 5-7:创建包含丰富点击事件的物化视图

CREATE MATERIALIZED VIEW CLICK_EVENTS_ENRICHED AS
SELECT e.*, c.*
FROM CLICK_EVENTS e
JOIN CUSTOMERS c on e.ipAddress = c.ipAddress

我们可以对 CUSTOMERS 表采取相同的步骤,并使用 PRODUCTS 表来将 CLICK_EVENTS 丰富为包含客户和产品信息的点击事件。

示例 5-8:创建包含丰富点击事件的物化视图

CREATE MATERIALIZED VIEW CLICK_EVENTS_ENRICHED AS
SELECT e.*, c.*, p.*
FROM CLICK_EVENTS e
JOIN CUSTOMERS c on e.ipAddress = c.ipAddress
JOIN PRODUCTS p on e.productid = p.productid

此时,我们已经完成了图 5-9 的第 5 步。现在,终端用户可以对 CLICK_EVENTS_ENRICHED 物化视图进行分析性拉取查询。流数据库的存储类型(基于行或列)将决定您可以调用的分析查询的复杂性。许多用例不需要人类调用拉取查询,应用程序本身可以消费实时的物化视图。

在某些情况下,应用程序可能希望使用行和列式流数据库来服务不同的低延迟分析查询。这将需要两个流数据库分别构建表的副本。如果你还记得,流平台允许发布和订阅 CDC 数据,以便多个系统构建其表的副本。

使用流数据库的 ELT

在第 2 章中,我们提到 ELT(提取、加载、转换)数据管道不支持实时用例,因为转换是在目标数据库中进行的。在这种情况下,数据库会将流数据静止化,从而迫使所有下游处理采用批处理语义。

然而,如果使用 ELT 的目标数据库是流数据库,那么这个管道仍然可以被视为实时的。这种在 ELT 中“加载”和“转换”部分之间的集成由流平台上的一个主题进行中介,流数据库从该主题消费数据。

有一个庞大的生态系统支持 ELT 解决方案,例如 dbt。结合流数据库,这些工具可以首次支持实时 ELT。而且因为流数据库在表面上表现得更像数据库,而不像流处理器,所以使用流数据库进行 ELT 实际上可以由之前在数据仓库中从事 ELT 工作的同一团队来实现。以这种方式,许多现在在管道后期(如数据仓库或数据湖)中运行的 ELT 作业可以移至实时流层。

总结

术语“流数据库”将流处理和数据库结合在一起。“数据库”通常与数据的批处理相关联。因此,将“流”和“数据库”结合在一起,也就将流处理与批处理,数据在动与数据静止结合了起来。

流数据库将 Martin Kleppmann 从数据库中抽出的两个结构——WAL(写前日志)和物化视图——重新引入数据库。

到目前为止,数据库 SQL 引擎仅支持静止的数据。通过允许物化视图在后台异步运行,我们可以使现有 SQL 同时处理静止数据和流动数据。这将数据库转变为流数据库。

流数据库在持久化层的实现方面有所不同。ksqlDB 使用 RocksDB 和主键索引。较新的流数据库使用更类似数据库的持久化层,以支持高效处理大量查询。

我们还知道,流数据库支持推和拉查询。推是在流数据库的“流处理”部分执行的。拉查询是在流数据库的“数据库”部分执行的。如果流数据库的“流处理”部分使用“数据库”部分作为其状态存储,那么你就拥有了一个真正的流数据库。

流数据库的存储类型决定了可以高效提供的拉查询类型。对于列式数据库,拉查询可以是分析性的,包括快速聚合。对于行式数据库,拉查询通常是更简单的查找查询,如点查询。

在图 5-10 中,我们展示了从行式到列式的流数据库范围。在左侧,拉查询通常由应用程序触发,这些应用程序是事件驱动的,不涉及人类参与。在右侧,拉查询通常由人类或仪表板触发。

流数据库简介在电子表格中,你可以在一个单元格中输入一个公式(例如,另一列中单元格的总和),并且当公式中的任何输入发生变化

流数据库的一个关键属性是一致性。在数据库中,一致性指的是数据始终有效,并遵循预定义的规则和约束的状态。它确保任何事务,无论成功与否,都能使数据库从一个一致的状态转移到下一个一致的状态,而不会违反完整性规则。在一个一致的数据库中,所有数据修改都遵循一组预定义的规则,确保数据保持准确和可靠。

在下一章中,我们将探讨一致性在流数据库(以及流处理器)中的重要性。

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