基于时间的操作,需要定义相关的时间语义和时间数据来源的信息。在Table API和SQL中,会给表单独提供一个逻辑上的时间字段,专门用来在表处理程序中指示时间
时间属性是每个表模式结构的一部分,它可以在创建表DDL里直接定义为一个字段,也可以在DataStream转换成表时,一旦定义了时间属性,就可以作为一个普通字段引用,并且可以在基于时间的操作中使用。
时间属性的数据类型为TIMESTAMP,它的行为类似于常规时间戳,可以直接访问并且进行计算
按照时间语义的不同,可把时间属性的定义分成:事件时间、处理时间
在实际应用中,最常用的就是事件时间,在事件时间语义下,允许表处理程序根据每个数据中包含的时间戳来生成结果。
事件时间语义最大的用途就是处理乱序事件或者延迟事件的场景,通过设置水位线(watermark)来表示事件时间的进展,而水位线可以根据数据的最大时间戳设置一个延迟时间,这样即使在出现乱序的情况下,对数据的处理也可以获得正确结果
为了处理无序事件,并区分流中的迟到事件,Flink需要从事件数据中提取时间戳,并生成水位线,用来推进事件时间的进展
在创建表的DDL(CREATE TABLE语句)中,可以增加一个字段,通过WATERMARK语句来定义事件时间属性。WATERMARK语句主要用来定义水位线的生成表达式,这个表达式会将带有事件时间戳的字段标记为事件时间属性,在其基础上给出水位线的延迟时间
CREATE TABLE EventTable(
user STRING,
url STRING,
ts TIMESTAMP(3),
WATERMARK FOR ts AS ts - INTERVAL '5' SECOND
) WITH (...);
ts字段定义为事件时间属性,基于ts设置5秒的水位线延迟。这里的5秒是以时间间隔的形式定义的,格式是INTERVAL<数值><时间单位>
INTERVAL '5' SECOND
Flink中支持的事件时间属性数据类型必须为TIMESTAMP或者TIMESTAMP_LTZ。这里TIMESTAMP_LTZ是指带有本地时区信息的时间戳(TIMESTAMP WITH LOCAL TIME ZONE);一般情况下如果数据中的时间戳是“年-月-日-时-分-秒”的形式,就是不带时区信息的,可以将事件时间属性定义为TIMESTAMP类型
如果原始的时间戳是一个长整型的毫秒数,这时就需要另外定义一个字段来表示事件时间熟悉,类型定义为TIMESTAMP_LTZ更方便
CREATE TABLE events (
user STRING,
url STRING,
ts BIGINT,
ts_ltz AS TO_TIMESTAMP_LTZ(ts, 3),
WATERMARK FOR ts_ltz AS time_ltz - INTERVAL '5' SECOND
) WITH (...);
这里另外定义了一个字段 ts_ltz,是把长整型的 ts 转换为 TIMESTAMP_LTZ 得到的;进而使用 WATERMARK 语句将它设为事件时间属性,并设置 5 秒的水位线延迟
事件时间属性也可以在将 DataStream 转换为表的时候来定义,调用fromDataStream()方法创建表时,追加参数来定义表中的字段结构,给某个字段加上rowtime()后缀,就表示将当前字段指定为事件时间属性。这个字段也可以是额外追加上去的逻辑字段,也可以是本身固有的字段,那么这个字段会被事件时间属性所覆盖,类型也会被转换为TIMESTAMP
这种方式只负责指定的时间属性,而时间戳的提取和水位线的生成应该之前就在DataStream上定义好了,由于DataStream中没有时区概念,因此Flink会将事件时间属性解析成不带时区的TIMESTAMP类型,所有的时间值都被当做UTC标准时间
// 方法一:
// 流中数据类型为二元组 Tuple2,包含两个字段;需要自定义提取时间戳并生成水位线
DataStream<Tuple2<String, String>> stream = inputStream.assignTimestampsAndWatermarks(...);
// 声明一个额外的逻辑字段作为事件时间属性
Table table = tEnv.fromDataStream(stream, $("user"), $("url"),$("ts").rowtime());
// 方法二:
// 流中数据类型为三元组 Tuple3,最后一个字段就是事件时间戳
DataStream<Tuple3<String, String, Long>> stream = inputStream.assignTimestampsAndWatermarks(...);
// 不再声明额外字段,直接用最后一个字段作为事件时间属性
Table table = tEnv.fromDataStream(stream, $("user"), $("url"),$("ts").rowtime());
系统时间,使用时不需要额外提取时间戳和生成水位线。因此在定义处理时间属性时,必须额外声明一个字段用来保存当前的处理时间
在创建表的DDL(CREATE TABLE 语句)中,可以增加一个额外字段,通过调用系统内置的PROCTIME()函数来指定当前的处理时间属性,返回的类型是TIMESTAMP_LTZ
CREATE TABLE EventTable(
user STRING,
url STRING,
ts AS PROCTIME()
) WITH (...);
处理时间属性同样可以在将DataStream转化为表的时候来定义,我们调用fromDataStream()方法创建表时,可用proctime()后缀来指定处理时间属性字段。由于处理时间是系统时间,原始数据中并没有这个字段,故在处理时间属性一定不能定义在一个已有字段上,只能定义在表结构所有字段的最后,作为额外的逻辑字段出现
DataStream<Tuple2<String, String>> stream = ...;
// 声明一个额外的字段作为处理时间属性字段
Table table = tEnv.fromDataStream(stream, $("user"), $("url"),$("ts").proctime());
窗口可以无界流切割成大小有限的桶来做计算,通过截取有限数据集来处理无限的数据,在DataStream API中提供了对不同类型的窗口进行定义和处理接口
Table API和SQL提供了一组分组窗口函数,常用的时间窗口如滚动窗口、滑动窗口、会话窗口都有对应的实现。具体在SQL中就是调用TUMBLE()、HOP()、SESSION(),传入时间属性字段、窗口大小等参数,以滚动窗口为例:
TUMBLE(ts, INTERVAL '1' HOUR)
进行窗口计算时,分组窗口是窗口本身当做一个字段对数据进行分组,可对组内的数据进行聚合,基本使用方式如下:
Table result = tableEnv.sqlQuery(
"SELECT " +
"user, " +
"TUMBLE_END(ts, INTERVAL '1' HOUR) as endT, " +
"COUNT(url) AS cnt " +
"FROM EventTable " +
"GROUP BY " + // 使用窗口和用户名进行分组
"user, " +
"TUMBLE(ts, INTERVAL '1' HOUR)" // 定义 1 小时滚动窗口
);
自1.13版本开始,Flink开始使用窗口表值函数(Windowing table-valued functions,Windowing TVFs)来定义窗口。窗口表值函数是Flink定义的多态表函数(PTF),可以将表扩展后返回,表函数可以看作是返回一个表的函数。
目前Flink提供的几个窗口TVF:滚动窗口、滑动窗口、累加窗口、会话窗口
窗口表值函数可以完全替代传统的分组窗口函数,窗口TVF更符合SQL标准,性能得到了优化,拥有更强大的功能,可以支持基于窗口的复杂计算,例如窗口Top-N、窗口联结等
在窗口TVF的返回值中,除去原始表中的所有列,还增加了用来描述窗口的额外3个列:窗口起始点(window_start)、窗口结束点(window_end)、窗口时间(window_time)
窗口时间的值 = window_end - 1ms,相当于窗口中能够包含数据的最大时间戳
(1)滚动窗口
滚动窗口在SQL中的概念与DataStream API中的定义完全一样,是长度固定、时间对齐、无重叠的窗口,一般用于周期性的统计计算
声明如下:
TUMBLE(TABLE EventTable, DESCRIPTOR(ts), INTERVAL '1' HOUR)
(2)滑动窗口
传入表名、时间属性、窗口大小、滑动步长
HOP(TABLE EventTable, DESCRIPTOR(ts), INTERVAL '5' MINUTES, INTERVAL '1' HOURS));
(3)累加窗口
滚动窗口和滑动窗口,可以用来计算大多数周期性的统计指标。不过在实际应用中还会遇到这样一类需求:我们的统计周期可能较长,因此希望中间每隔一段时间就输出一次当前的统计值;与滑动窗口不同的是,在一个统计周期内,我们会多次输出统计值,它们应该是不断叠加累积的
例子:按天来统计网站的PV,如果用1天的滚动窗口,那需要每天24点才会计算一次,输出频率太低;如果用滑动窗口,计算频率变高,统计的变成过去24小时的PV。若我们希望按照日统计每天的PV,不过需要每隔1小时就输出一次当天到目前为止的PV值,这种特殊的窗口就叫做累加窗口
累加窗口时窗口TVF中新增的窗口功能,会在一定的统计周期内进行累加计算。累加窗口中的两个核心参数:
CUMULATE(TABLE EventTable, DESCRIPTOR(ts), INTERVAL '1' HOURS, INTERVAL '1' DAYS))
上述基于时间属性ts,在表EventTable上定义了一个统计周期为1天、累加步长为1小时的累加窗口。