一、DataStream上的关系查询
下表比较了传统的关系代数和流处理的输入数据、执行和输出结果的关系
关系代数/SQL | 流处理 |
---|---|
关系(或表)时有界(多)元组集合 | 流是一个无限元组序列 |
对批数据(例如关系数据库中的表)执行 的查询可以访问完整的输入数据 | 流式查询在启动时不能访问所有数据,必须“等待” 数据流入 |
批处理查询在产生固定大小的结果后终止 | 流查询不断地根据接收到的记录更新其结果,并且始终不会结束 |
尽管存在这些差异,但是使用关系查询和SQL处理流并不是不可能的。高级关系数据库系统提供了一个称为物化视图的特性。物化视图被定义为一条SQL查询,就像常规的虚拟视图一样;与虚拟视图相反,物化视图缓存查询的结果,因此在访问视图时不需要对查询进行计算。但是缓存的难题是防止缓存过期。当其定义查询的基表被修改时,物化视图将过期。*即时视图维护(Eager View Maintenance)*是一种一旦更新了物化视图的基表就立即更新视图的技术。
如果我们考虑以下问题,那么即时视图维护和流上的SQL查询之间的联系就显而易见:
- 数据库表是INSERT、UPDATE、DELETE DML语句的stream的结果,通常称为changelog stream;
- 物化视图被定义为一条SQL查询。为了更新视图,查询不断地处理视图的基本关系的changelog stream(DataStream就是处理changelog stream,所以物化视图基表的更新和DataStream的中数据的更新);
- 物化视图是流式SQL查询的结果。
二、动态表和连续查询
2.1 动态表
动态表是Flink支持流数据的Table API和SQL的核心概念。与表示批处理数据的静态表不同,动态表是随时间变化的。可以像查询静态批处理表一样查询它们。查询动态表将生成一个连续查询。一个连续查询永远不会终止,结果会生成一个动态表。查询不断更新其动态结果表,以反映其动态输入表上的更改**。本质上,动态表上的连续查询非常类似于定义物化视图的查询。**
下图显示了流、动态表和连续查询之间的关系:
- 将流转成动态表
- 在动态表上计算一个连续查询,生成一个新的动态表
- 生成的动态表被转换回流
2.2 连续查询
在动态表上计算一个连续查询,并生成一个新的动态表。与批处理查询不同,连续查询从不终止,并根据其输入表上的更新来更新其结果表。在任何时候,连续查询的结果在语义上与以批处理模式在输入表快照上执行的相同查询的结果相同。
在接下来的代码中,我们将展示clicks表上的两个示例查询,这个表是在点击事件流上定义的。
第一个查询是一个简单的 group-by count聚合查询,下面的图显示了当clicks表被附加的行更新时,查询时如何被计算的。
当查询开始,clicks 表(左侧)是空的。当第一行数据被插入到 clicks 表时,查询开始计算结果表。第一行数据 [Mary,./home] 插入后,结果表(右侧,上部)由一行 [Mary, 1] 组成。当第二行 [Bob, ./cart] 插入到 clicks 表时,查询会更新结果表并插入了一行新数据 [Bob, 1]。第三行 [Mary, ./prod?id=1] 将产生已计算的结果行的更新,[Mary, 1] 更新成 [Mary, 2]。最后,当第四行数据加入 clicks 表时,查询将第三行 [Liz, 1] 插入到结果表中。
第二条查询与第一条类似,但是除了用户属性之外,还将 clicks 分组至每小时滚动窗口中,然后计算 url 数量(基于时间的计算,例如基于特定时间属性的窗口,后面会讨论)。同样,该图显示了不同时间点的输入和输出,以可视化动态表的变化特性。
与前面一样,左边显示了输入表 clicks。查询每小时持续计算结果并更新结果表。clicks表包含四行带有时间戳(cTime)的数据,时间戳在 12:00:00 和 12:59:59 之间。查询从这个输入计算出两个结果行(每个 user 一个),并将它们附加到结果表中。对于 13:00:00 和 13:59:59 之间的下一个窗口,clicks 表包含三行,这将导致另外两行被追加到结果表。随着时间的推移,更多的行被添加到 click 中,结果表将被更新。
2.3 更新和追加查询
这两个示例虽然看起来非常相似(都计算分组计数聚合),但它们在一个重要方面不同:
- 第一个查询更新先前输出的结果,即定义结果表的changelog流包含INSERT和UPDATE操作;
- 第二个查询在结果表上做追加操作,即结果表的changelog流至包含INSERT操作
一个查询是产生一个只追加的表还是一个更新的表有以下区别:
- 产生更新的查询必须维护更多的状态;
- 将append-only的表转换为流与将update的表转换成流是不同的(具体参考第三章表到流的转换)
三、表到流的转换
Table to Stream
动态表可以像普通数据库表一样通过INSERT、UPDATE和DELETE来不断修改。它可能是一个insert-only的表,也可能是一个不断有UPDATE或者DELETE的表。
在将动态表转化为流或将其写入外部系统时,需要对这些更改进行编码。Flink的Table API和SQL支持三种方式来编码一个动态表的变化:
-
Append-only stream:仅通过INSERT操作来修改的动态表可以被转成插入行的流
-
Retract stream: 撤回流是包含两种消息类型的流:add messages和 retract messages。通过将INSERT操作编码为add message、将DELETE操作编码为retract message、将UPDATE操作编码为两条消息:retract messag用来撤回之前的行和一个add message用来表示新的数据。下面的图展示了动态表到retract stream的转换:
-
Upsert stream: upsert流也是包含两种消息类型的流:upsert message和delete message。转换为upsert流的动态表需要主键。通过将INSERT和UPDATE操作编码为upsert message、将DELETE操作编码为delete message来将具有唯一键的动态表转换为流。消费流的算子需要知道主键以便正确地处理数据。与 retract 流的主要区别在于
UPDATE
操作是用单个 message 编码的,因此效率更高。下图显示了将动态表转换为 upsert 流的过程。
因为Flink流数据的不可变性,发出去的数据不能撤回,所以Flink把update操作分成两个指令,DELETE和ADD,即先发出去一条数据是ADD,后面有更新了,就DELETE,然后再ADD新的;
对于外部存储,可以直接用Upsert Stream,这样做效率会更高,因为update只是一条指令,Kafka 也有upsert kafka
https://zhuanlan.zhihu.com/p/338604371
Flink hash join、sort merge join、nested loop join