该翻译原文来之flink官网dynamic_tables.html
SQL和关系代数的设计还没有考虑涉及到流数据中。因此,在关系代数(和SQL)和流处理之间没有什么概念上的差异。
本页面讨论了这些差异,并解释了Flink如何在无界数据上实现与在有界数据上使用常规数据库引擎获得相同的语义。
下表比较了关与输入数据、执行和输出结果相关的传统关系代数和流处理。
关系代数/SQL | 流处理 |
---|---|
关系(或表)是有界的(多个)元组集合。 | 流是元组的无限序列。 |
在批处理数据(例如关系数据库中的表)上执行的查询可以访问完整的输入数据。 | 流查询不能在启动时访问所有数据,必须“等待”数据的流入。 |
批处理的查询在它生成固定大小的结果后终止。 | 流查询会根据接收到的记录不断地更新它的结果,并且永远不会完成。 |
尽管有这些差异,使用关系查询和SQL处理流并非不可能的。高级关系数据库系统提供了一个称作物化视图的特性。物化视图被定义为SQL查询,就和普通的虚拟视图一样。与虚拟视图不同的是,物化视图缓存查询的结果,以便在访问视图时不需要计算查询。对于缓存的一个常见挑战是防止缓存提供过时的结果。当其定义查询的基本表被修改时,物化视图就会过时。即时视图维护是一种技术,它可以在基本表更新后立即更新物化视图。
如果我们考虑接下来的问题,那么急切视图维护和流上的SQL查询之间的关系就变得很明显了:
考虑到这些要点,我们将在下一节中介绍动态表的概念。
动态表是Flink的表API和对流数据的SQL支持的核心概念。与表示批处理数据的静态表不同,动态表是随时间变化的。可以像查询静态批处理表一样查询它们。查询动态表产生连续查询。连续查询永远不会终止,并最终生成一个动态表。查询不断更新其(动态)结果表,以反映对其(动态)输入表的更改。本质上,动态表上的连续查询非常类似于定义物化视图的查询。
需要注意的是,连续查询的结果在语义上总是等价于在输入表快照上以批处理模式执行相同查询的结果。
注意:动态表首先是一个逻辑概念。在查询执行期间,动态表不一定(完全)物化。
在接下来,我们将用具有以下模式的单击事件流解释动态表和连续查询的概念
[
user: VARCHAR, // 用户的名称
cTime: TIMESTAMP, // 访问URL的时间
url: VARCHAR // 用户访问的URL
]
为了使用关系查询处理流,必须将流转换为表。从概念上讲,流的每个记录都被解释为结果表上的插入修改。从本质上说,我们正在从一个仅插入的更改日志流构建一个表。
下图显示了单击事件流(左侧)如何转换为表(右侧)。随着单击流的记录越来越多,结果表不断增长。
注意:在流上定义的表在内部没有物化。
在动态表上计算连续查询,并生成一个新的动态表。与批处理查询不同,连续查询从不根据输入表上的更新终止和更新结果表。在任何时间点,连续查询的结果在语义上等价于在输入表快照上以批处理模式执行相同查询的结果。
下面,我们将展示在click事件流上定义的click表上的两个查询示例。
第一个查询是一个简单的按计数聚合查询。它在user字段上对click表进行分组,并计算访问的url的数量。下图显示了在使用额外的行更新click表时,查询是如何随时间进行评估的。
启动查询时,单击表(左侧)为空。当第一行插入到clicks表中时,查询开始计算结果表。插入第一行[Mary,…/Home]后,结果表(右侧,顶部)由一行[Mary,1]组成。当第二行[bob,…/cart]插入到clicks表中时,查询将更新结果表并插入新行[bob,1]。第三排[Mary,…/Prod?id=1]生成已计算结果行的更新,以便将[Mary,1]更新为[Mary,2]。最后,当第四行附加到clicks表时,查询将第三行[liz,1]插入到结果表中。
第二个查询与第一个查询类似,但是在计算url数量之前,除了user属性外,还会在每小时滚动窗口上对单击表进行分组(基于时间的计算,例如windows,是基于特殊的时间属性的,稍后将对此进行讨论)。图中再次显示了在不同时间点的输入和输出,以可视化动态表的变化特性。
与以前一样,输入表单击显示在左侧。查询每小时持续计算结果并更新结果表。单击表包含四行时间戳(cTime),时间戳位于12:00到12:59:59之间。查询从该输入计算两个结果行(每个用户一个),并将它们追加到结果表中。对于在13:00:00到13:59:59之间的下一个窗口,clicks表包含三行,这将导致向结果表追加另外两行。随着时间的推移,随着更多的行被追加到单击,结果表将被更新。
虽然这两个示例查询看起来非常相似(都计算了一个分组计数聚合),但它们在一个重要方面有所不同:
查询是否生成仅追加的表或更新的表具有一定的含义:
许多(但不是所有)语义上有效的查询可以作为流上的连续查询进行计算。有些查询太昂贵而无法计算,这可能是因为它们需要维护的状态的大小,也可能是因为计算更新太昂贵。
SELECT user, COUNT(url)
FROM clicks
GROUP BY user;
SELECT user, RANK() OVER (ORDER BY lastLogin)
FROM (
SELECT user, MAX(cTime) AS lastAction FROM clicks GROUP BY user
);
查询配置页面讨论控制连续查询执行的参数。一些参数可以用来交换维护状态的大小以获得结果的准确性。
动态表可以像常规数据库表一样,通过插入、更新和删除更改持续修改。它可能是一个表,其中只有一行,不断更新;可能是一个只有插入的表,没有更新和删除修改;
在将动态表转换为流或将其写入外部系统时,需要对这些更改进行编码。Flink的表API和SQL支持三种对动态表的更改进行编码的方法:
仅追加流:仅通过插入更改修改的动态表可以通过发出插入的行转换为流。
回撤流:回撤流是具有两种消息类型的流,即添加消息和回撤消息。通过将插入更改编码为添加消息、删除更改编码为回撤消息、更新更改编码为已更新(上一行)的撤消消息和更新(新行)的添加消息,可以将动态表转换为撤消流。下图可视化了将动态表转换为回撤流。
Upsert流:Upsert流包含两种类型的消息,Upsert消息和delete消息。转换为upsert流的动态表需要(可能是组合的)惟一键。将插入和更新更改编码为upsert消息,将删除更改编码为DELETE消息,从而将具有惟一键的动态表转换为流。流使用操作符需要知道惟一键属性,以便正确应用消息。与回撤流的主要区别在于更新更改是用单个消息编码的,因此更有效。
在Common Concepts页面中讨论了将动态表转换为DataStream的API。请注意,在将动态表转换为DataStream时,只支持追加和回撤流。在TableSources和 TableSinks的页面上讨论了将动态表发送到外部系统的TableSink接口。