数据流和动态表 | 青训营笔记

109 阅读9分钟

这是我参与「第四届青训营 」笔记创作活动的第15天

1 概述

SQL 和关系代数在设计之初就针对的是静态的数据静态数据是有界的,因此可以很容易地和表(关系)进行映射。但是对于一个不断变化的实时数据流而言,数据是无边界不断更新的,在将 SQL 应用在流上的时候,势必需要考虑数据的更新问题。例如,对与聚合操作而言,随着新数据源源不断地到达,聚合的结果必然是需要不断更新的。在这种情况下,目前包括 Flink、Calcite、Beam、 Kafka 等社区的开发人员一起在推动 Streaming SQL 的标准化,流和动态表的是这些工作的基础

2 流和动态表

我们知道,从数据库的角度来看,一张表可以看作是一系列 Change log(INSERT、UPDATE、DELETE)聚合的结果如果每一条 Change log 对应实时数据流中的一条消息,那么一张表和一个 Change log 的数据流就是可以互相转换的:

  • The aggregation of a stream of updates over time yields a table
  • The observation of changes to a table over time yield a stream
  • 随着时间的推移,更新流的聚合生成一个表
  • 观察表随时间的变化产生流

在这样一个基本概念的指导下,将 SQL 应用到流上就是完全可能的,其难点就在于如何处理计算结果的更新。为此,社区提出了动态表的概念,通过将计算结果转换为 INSERT、UPDATE、DELETE 这三种类型的消息来完成到动态表的映射。关于动态表的更细致的介绍,可以查看Flink官方的文档和这个演讲Foundations of Streaming SQL [Qcon London 2018],以获得更深入的了解。

SQL - 和 Table API - 为实时数据处理提供灵活而强大的功能。本页描述了关系概念如何优雅地转换为流式传输,从而允许 Flink 在无界流上实现相同的语义。

3 动态表的具体描述

Flink关于动态表的解释

1 动态表和连续查询

动态表是 Flink 的 Table API 和 SQL 支持流数据的核心概念。

  • 与表示批处理数据的静态表相比,动态表随时间而变化。
  • 但就像静态批处理表一样,系统可以对动态表执行查询。
  • 查询动态表会产生一个Continuous Query
  • 连续查询永远不会终止并产生动态结果 - 另一个动态表。
  • 查询不断更新其(动态)结果表以反映其(动态)输入表的变化。
  • 本质上,对动态表的连续查询与定义物化视图的查询非常相似。

需要注意的是,连续查询输出在语义上始终等同于在输入表的快照上以批处理模式执行的相同查询的结果。

下图可视化了流、动态表、连续查询的关系:

  1. 流被转换为动态表。
  2. 对动态表进行连续查询评估,生成一个新的动态表。
  3. 生成的动态表被转换回流。

动态表首先是一个逻辑概念。在查询执行期间,动态表不一定(完全)具体化。

在下文中,我们将使用具有以下架构的点击事件流来解释动态表和连续查询的概念:

**CREATE** **TABLE** clicks ( **user** VARCHAR, *-- the name of the user* url VARCHAR, *-- the URL that was accessed by the user* cTime **TIMESTAMP**(3) *-- the time when the URL was accessed* ) **WITH** (...);

2 在流上定义表

  • 使用关系查询处理流需要将其转换为Table. 从概念上讲,流的每条记录都被解释为INSERT对结果表的修改。INSERT表示我们正在从INSERT-only 的changelog流构建一个表。
  • 下图可视化了点击事件流(左侧)如何转换为表格(右侧)。
  • 随着更多点击流记录的插入,生成的表格会不断增长。

请记住,在流上定义的表在内部未实现。

3 连续查询


  • 对动态表评估连续查询并生成一个新的动态表作为结果。

  • 与批处理查询相比,连续查询永远不会终止并根据其输入表的更新更新其结果表。

  • 在任何时间点,连续查询在语义上等同于在输入表的快照上以批处理模式执行的相同查询的结果。

  • 在下文中,我们展示了clicks对单击事件流上定义的表的两个示例查询。

    1. 第一个查询是一个简单的GROUP-BY COUNT聚合查询。clicks它对字段上的表进行分组user并计算访问的 URL 的数量。下图显示了随着时间的推移,随着clicks使用其他行更新表,查询是如何评估的。

    1. 查询开始时,clicks表(左侧)为空。该查询在插入第一行时计算结果表。第一行[Mary, ./home]到达后,结果表(右侧,顶部)由一行组成[Mary, 1]。当第二行[Bob, ./cart]插入到clicks表中时,查询会更新结果表并插入新行[Bob, 1]。第三行[Mary, ./prod?id=1]产生对已计算结果行[Mary, 1]的更新,以便更新为[Mary, 2][Liz, 1]最后,当第四行附加到表中时,查询将第三行插入结果clicks表。
    2. 第二个查询类似于第一个查询,但clicks除了user属性之外,还在每小时翻滚窗口上对表进行分组,然后再计算 URL 的数量(基于时间的计算,例如窗口是基于特殊时间属性的,稍后将讨论)。同样,该图显示了不同时间点的输入和输出,以可视化动态表的变化性质。

    1. 和以前一样,输入表clicks显示在左侧。查询每小时连续计算结果并更新结果表。clicks 表包含四行,时间戳 ( cTime) 介于12:00:00和之间12:59:59。查询从此输入计算两个结果行(每个行一个user)并将它们附加到结果表中。对于13:00:00和之间的下一个窗口13:59:59,该clicks表包含三行,这导致另外两行附加到结果表中。随着时间的推移,结果表会随着更多行的追加而更新clicks

4 更新和追加查询

尽管这两个示例查询看起来非常相似(都计算分组计数聚合),但它们在一个关键方面有所不同:

  • 第一个查询更新先前发出的结果,即定义了包含INSERT,UPDATE结果表的changelog流。
  • 第二个查询仅附加到结果表,即结果表的更改日志流仅包含INSERT更改。

查询是否生成仅追加表或更新表具有一些含义:

  • 进行更新更改的查询通常必须保持更多状态(请参阅下一节)。
  • 仅追加表到流的转换与更新表的转换不同(请参阅表到流的转换部分)。

5 查询限制

许多(但不是全部)语义上有效的查询可以作为对流的连续查询进行评估。一些查询的计算成本太高,要么是因为它们需要维护的状态大小,要么是因为计算更新成本太高。

  • 状态大小:

    连续查询在无限流上进行评估,通常应该运行数周或数月。因此,连续查询处理的数据总量可能非常大。必须更新先前发出的结果的查询需要维护所有发出的行以更新它们。例如,第一个示例查询需要存储每个用户的 URL 计数以增加计数并在输入表接收到新行时发送新结果。如果只跟踪注册用户,那么要维护的计数可能不会太高。但是,如果为非注册用户分配了唯一的用户名,则要维护的计数数量会随着时间的推移而增加,并最终可能导致查询失败。

    **SELECT** **user**, **COUNT**(url) **FROM** clicks **GROUP** **BY** **user**;

  • 计算更新: RANKclickslastAction

    即使只添加或更新了一条输入记录,某些查询也需要重新计算和更新大部分已发出的结果行。这样的查询不太适合作为连续查询执行。一个示例是以下查询,它根据

    最后一次点击的时间为每个用户计算等级。一旦表接收到新行,就会更新用户并计算新的排名。但是,由于两行不能具有相同的排名,所有排名较低的行也需要更新。

    **SELECT** **user**, RANK() OVER (**ORDER** **BY** lastAction) **FROM** ( **SELECT** **user**, **MAX**(cTime) **AS** lastAction **FROM** clicks **GROUP** **BY** **user** );

查询配置页面讨论了控制连续查询执行的参数。一些参数可用于交换保持状态的大小以换取结果的准确性。

6 表到流的转换

动态表可以像常规数据库表一样通过INSERTUPDATEDELETE不断修改和更改。它可能是一个单行的表,一个不断更新的表,一个只插入的表,没有UPDATE和DELETE,或者介于两者之间。

在将动态表转换为流或将其写入外部系统时,需要对这些更改进行编码。Flink 的 Table API 和 SQL 支持三种方式来编码动态表的变化:

  • **仅追加流(Append-only):**仅通过更改修改的动态表INSERT可以通过发出插入的行来转换为流。

  • **撤回流(Retract):**撤回流是具有两种类型消息的流,添加消息撤回消息。动态表被转换为撤消流可以通过以下方法:INSERT更改为添加消息、DELETE更改为撤消消息、UPDATE:对(前一个)行更改为撤消消息,对(新)行为附加消息, 下图可视化了动态表到撤回流的转换。

  • Upsert 流: upsert流是具有两种消息类型的流,即upsert消息和delete消息。转换为upsert流的动态表需要(可能是复合的)唯一键。通过将插入和更新更改编码为upsert消息,并将删除更改编码为DELETE消息,将具有唯一键的动态表转换为流。流使用操作员需要知道唯一键属性才能正确应用消息。与retract流的主要区别在于,更新更改使用单个消息进行编码,因此效率更高。下图显示了将动态表转换为upsert流的过程。

将动态表转换为数据流的 API在Common Concepts页面上进行了讨论。请注意,将动态表转换为数据流时仅支持添加和撤销. 表资源和表链接页面讨论了向外部系统发送动态表的表链接接口。