流概念
对于流和批处理程序,Table API和SQL是统一支持的。这就意味着Table API和SQL的查询有相同的语义,不管输入是一个有界的批输入还是无界的流输入。因为关系代数和SQL最初是为批处理程序设计的,无界流输入的关系查询不如有界批处理的关系查询好理解。
在这个页面,我们讲解概念,实践的局限性和流数据关系API的具体配置参数。
流数据的关系查询
SQL和关系代数还没有考虑流式数据的设计。因此,关系代数(SQL)和流处理在概念上的差距很小。
关系代数/SQL
1.关系和表示有界的多元组集合
2.在批处理数据上执行的一个查询可以访问完整的输入数据(例如:关系数据库)
3.批处理查询在生成一个固定大小的结果后终止
流处理
1.流是一个无界序列的元组
2.流查询在启动时不能访问所有的数据,必须等待数据流进来。
3.流查询根据其接收到的数据不断的更新结果,从不完成。
尽管有这么多差异,处理流的关系查询和SQL也不是不可能的。高级的关系数据库系统提供了一个物化视图的特性。物化视图被定义为SQL查询,就像一个常规的虚拟视图,与虚拟视图相反,物化视图缓存查询结果,这样在访问视图时,不需要对查询进行评估。缓存最常见的挑战是缓存过时的数据。当定义查询的基本表被修改时,一个物化视图就过期了。Eager View Maintenance是一种更新物化视图并在其基本表更新后更新物化视图的技术。
如果我们考虑下面的问题,流视图和SQL查询之间的连接就变得很明显了:
1.一个数据库表是一个流的插入、更新和删除DML语句的结果,通常被称为更新流
2.物化视图被定义为SQL查询,为了更新视图,查询是连续不断的处理视图基本关系流的更新日志
3.流式SQL的查询结果是物化视图。
动态表和连续查询
动态表示Table API和SQL支持流数据的核心概念。与表示批处理的静态表先相比,动态表随着时间变化更新。可以像静态批处理那样查询它们。查询一个动态表会产生连续查询。连续查询从不终止,并且生成一个动态表作为结果。查询不断的更新结果表反映输入表的变化。基本上,动态表的连续查询非常类似于物化视图的定义的查询。
需要注意的是连续查询的结果总是在语义上等同于在输入表上以批处理模式执行的查询结果
下图显示了流、动态表和连续查询的关系
流----->动态表---->连续查询---->动态表---->流
1.流可以被转化为一个动态表
2.动态表上进行连续查询会产生一个新的动态表
3.结果动态表可以被转换回成一个流
注意:动态表是最重要的一个逻辑概念。在执行查询的过程中,动态表不一定完全实现。
下面我们用一个含有以下sehema的单击事件流讲解动态表和连续查询的概念:
[
user: VARCHAR, // the name of the user
cTime: TIMESTAMP, // the time when the URL was accessed
url: VARCHAR // the URL that was accessed by the user
]
定义流上的表
为了用关系查询处理流,它必须被转化成一张表。从概念上讲,流的每一条记录被解释为对结果表的插入修改。基本上,我们从流的插入变更记录构建一个表。
下图显示了一个单击事件流是如何转换成一个表的。随着流更多的记录被插入,结果表不断的增长。
注意:在留上定义的表在内部不是物化的。
连续查询
连续查询被计算于一个动态表上,并产生一个新的动态表作为结果。与批处理查询相反,一个连续查询从不中断,并且根据输入表的更新来更新它的结果表。在任何时间点,一个连续查询的结果在语义上等价于在输入表上以批处理模式执行的查询结果。
下面我们展示了两个例子查询一个单击事件流上定义的单击表。
第一个查询是一个简单的分组、统计聚合查询。根据user字段将单击表分组并统计URL的访问次数。下面的图展示了单击表随着时间的推移额外的增加行是如何查询计算的。
当查询启动时,这个单击表(左手边)是空的,当第一条数据被插入到单击表中,查询开始计算结果表,第一条数据 [Mary, ./home] 被插入后,结果表(右手边,顶部那个)由[Mary, 1]一行组成。当第二条数据[Bob, ./cart]被插入单击表后,查下更新结果表并插入了新的一行[Bob, 1]。第三行[Mary, ./prod?id=1] 产生一个已经计算好结果行的更新,由[Mary, 1] 更新为 [Mary, 2]。最后,当第第四条数据被追加到单击表中后,查询会往结果表插入第三条数据[Liz, 1]。
第二个查询类似于第一个查询,不过单击表除了按用户属性分组外还有一个按小时滚动的窗口,在统计URL数量之前(基于时间的计算就像基于一个特殊时间属性的窗口)。再一次,下图显示了不同时间点的输入和输出,可视化展示的动态表的变化。
如之前,左边显示的单击表输入。查询每小时连续不断的计算结果,并更新结果表。单击表根据时间戳从12:00:00 到 12:59:59 之间包含4条数据。查询根据输入(每个用户一个)计算出两行结果并追加到结果表。在下一个13:00:00到13:59:59窗口,单击表包含3行数据,另外的两行结果被追加到结果表,随着时间的推移,更多的数据被追加到单击表,导致结果表被更新。
查询的更新和追加
尽管这两个查询例子看起来比较类似(二者都是分组聚合统计),他们在一个重要的方面有所不同,第一个查询更新以前发射结果,更新流定义的结果表包含 INSERT 和 UPDATE 变更。第二个查询仅仅追加结果表,更新流定义的结果表紧由INSERT组成。
无论查询只生成一个仅追加的表或者一个更新的表有一些含义,产生更新变更的查询通常要保持更多的状态(见下面的章节)。追加表转化为流不同于更新表(见 表转换为流 章节)。
查询限制
很多,但也不是全部,在流上,可以对语义有效的查询进行计算作为一个连续查询,有些的查询的计算开销太大,要么由于他们要需要维护state的大小,要么计算更新太贵了。
State大小
一个无界流上的连续查询被计算通常会通常要跑几个星期或几个月,因此,连续查询的数据总量可能会非常大。必须更新以前发出结果的查询需要维护所有发的行以便能够更新他们。例如,第一个查询需要存储每一个用户的URL统计以便能够增加统计并且发出一个新的结果,当输入表接收大一条新的数据,如果只跟踪注册用户,保持的数量可能不会太高。然而,一个非注册的用户获得一个唯一的用户名,要维护的的数可能会随着时间的增长而增长,最终导致查询失败。
SELECT user,COUNT(url) FROM clicks GROUP BY user;
计算更新
一些查询需要重新计算并更新发射结果行的很大一部分,即使只有单个的一条记录被添加或更新,显而易见,这种查询不太适合作为连续查询执行,下面的查询例子是根据最后一次单击的时间计算每一个用户的等级。只要单击表接收一条新的记录,用户的最后一个动作被更新且一个新的等级被计算,然而由于两行不能有相同的等级,所有的低等级的行也需要被更新。
SELECT user,RANK()OVER(ORDERBYlastLogin)FROM(SELECTuser,MAX(cTime)ASlastActionFROMclicksGROUPBYuser);
QueryConfig这个章节讨论了控制执行连续查询的参数。有些参数可以用来维护状态的大小用于结果的准确性
表转换为流
一个动态表可以被INSERT、UPDATE和DELETE连续的修改就像一个普通的数据库表。他可能是一个单行的表,不断的更新,一个只插入的表,没有UPDATE和DELETE修改,或者两者之间的任何东西。
当将动态表转换为流或者写入其他外部系统时,这些改变需要编码。Flink的 Table API&SQL支持3中办法编码改变一个动态表
只追加的流
一个只被 INSERT 变更修改的动态表可以通过插入行来转换成流
回缩流
回缩流是具有两种消息类型的流,添加消息和撤回消息,一个动态表被转换成回缩流,通过编写一个插入变更作为添加消息,一个删除变更作为撤回消息,和一个更新变更作为撤回消息,更新前的作为撤回消息,更新后的新行作为添加消息。下图示意一个动态表转为一个回缩流。
更新插入流(upsert)
一个更新插入流是有两个类型的消息流,更新插入消息和删除消息,一个动态表转换成一个更新插入流需要一个唯一的Key(可能复合),具有唯一键的动态表被转换成动态表是通过编写INSERT和UPDATE变更作为更新插入消息,删除变更作为删除消息。流的消耗操作符需要知道唯一的Key属性,以便能够正确的应用消息。与回溯流最主要的不同是编写更新变更是一个单独的消息,也因此更高效。下图示意了一个动态表转换成更新插入流
把动态表转换为DataStream的API在通用概念这个页面讨论。请注意当转换动态表为DataStream是指支持追加流和回缩流。TableSink接口发射一个动态表到一个外部系统在TableSources and TableSinks页面讨论。
时间属性
Flink能够基于不同的时间点处理流数据。
Processing time 指的是各自执行操作的机器的系统时间。
Event time 指的是基于每一行的时间戳处于流数据。时间戳可以编写为事件发生的时间。
Ingestion time 是事件进入Flink的时间,本质上和Event time类似
关于更多的Flink的时间的处理,详见Event Time and Watermarks.介绍
表程序需要为流式环境指定响应的时间特性。
Table API 和SQL 二者基于时间的操作如同窗口需要时间的概念信息和它的起点。因此。表可以提供逻辑时间属性为指示时间及访问相应的时间戳在表程序中。