Flink总共有三种时间语义:Processing time(处理时间)、Event time(事件时间)以及Ingestion time(摄入时间)。关于这些时间语义的具体解释,可以参考另一篇文章Flink的时间与watermarks详解。本文主要讲解Flink Table API & SQL中基于时间的算子如何定义时间语义。通过本文你可以了解到:
时间属性简介
Flink TableAPI&SQL中的基于时间的操作(如window),需要指定时间语义,表可以根据指定的时间戳提供一个逻辑时间属性。
时间属性是表schama的一部分,当使用DDL创建表时、DataStream转为表时或者使用TableSource时,会定义时间属性。一旦时间属性被定义完成,该时间属性可以看做是一个字段的引用,从而在基于时间的操作中使用该字段。
时间属性像一个时间戳,可以被访问并参与计算,如果一个时间属性参与计算,那么该时间属性会被雾化成一个常规的时间戳,常规的时间戳不能与Flink的时间与水位线兼容,不能被基于时间的操作所使用。
Flink TableAPI & SQL所需要的时间属性可以通过Datastream程序中指定,如下:
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
|
处理时间
基于本地的机器时间,是一种最简单的时间语义,但是不能保证结果一致性,使用该时间语义不需要提取时间戳和生成水位线。总共有三种方式定义处理时间属性,具体如下
DDL语句创建表时定义处理时间
处理时间的属性可以在DDL语句中被定义为一个计算列,需要使用PROCTIME()函数,如下所示:
CREATE TABLE user_actions ( user_name STRING, data STRING, user_action_time AS PROCTIME() ) WITH ( ... );
SELECT TUMBLE_START(user_action_time, INTERVAL '10' MINUTE), COUNT(DISTINCT user_name) FROM user_actions GROUP BY TUMBLE(user_action_time, INTERVAL '10' MINUTE);
|
DataStream转为Table的过程中定义处理时间
在将DataStream转为表时,在schema定义中可以通过.proctime属性指定时间属性,并将其放在其他schema字段的最后面,具体如下:
DataStream<Tuple2<String, String>> stream = ...;
Table table = tEnv.fromDataStream(stream, "user_name, data, user_action_time.proctime");
WindowedTable windowedTable = table.window(Tumble.over("10.minutes").on("user_action_time").as("userActionWindow"));
|
使用TableSource
自定义TableSource并实现DefinedProctimeAttribute
接口,如下:
public class UserActionSource implements StreamTableSource<Row>, DefinedProctimeAttribute {
@Override public TypeInformation<Row> getReturnType() { String[] names = new String[] {"user_name" , "data"}; TypeInformation[] types = new TypeInformation[] {Types.STRING(), Types.STRING()}; return Types.ROW(names, types); }
@Override public DataStream<Row> getDataStream(StreamExecutionEnvironment execEnv) { DataStream<Row> stream = ...; return stream; }
@Override public String getProctimeAttribute() { return "user_action_time"; } }
tEnv.registerTableSource("user_actions", new UserActionSource());
WindowedTable windowedTable = tEnv .from("user_actions") .window(Tumble.over("10.minutes").on("user_action_time").as("userActionWindow"));
|
事件时间
基于记录的具体时间戳,即便是存在乱序或者迟到数据也会保证结果的一致性。总共有三种方式定义处理时间属性,具体如下
DDL语句创建表时定事件时间
事件时间属性可以通过 WATERMARK语句进行定义,如下:
CREATE TABLE user_actions ( user_name STRING, data STRING, user_action_time TIMESTAMP(3), WATERMARK FOR user_action_time AS user_action_time - INTERVAL '5' SECOND ) WITH ( ... );
SELECT TUMBLE_START(user_action_time, INTERVAL '10' MINUTE), COUNT(DISTINCT user_name) FROM user_actions GROUP BY TUMBLE(user_action_time, INTERVAL '10' MINUTE);
|
DataStream转为Table的过程中定义事件时间
当定义Schema时通过.rowtime属性指定事件时间属性,必须在DataStream中指定时间戳与水位线。例如在数据集中,事件时间属性为event_time,此时Table中的事件时间字段中可以通过’event_time. rowtime‘来指定。
目前Flink支持两种方式定义EventTime字段,如下:
DataStream<Tuple2<String, String>> stream = inputStream.assignTimestampsAndWatermarks(...);
Table table = tEnv.fromDataStream(stream, "user_name, data, user_action_time.rowtime");
DataStream<Tuple3<Long, String, String>> stream = inputStream.assignTimestampsAndWatermarks(...);
Table table = tEnv.fromDataStream(stream, "user_action_time.rowtime, user_name, data");
WindowedTable windowedTable = table.window(Tumble.over("10.minutes").on("user_action_time").as("userActionWindow"));
|
使用TableSource
另外也可以在创建TableSource的时候,实现DefinedRowtimeAttributes接口来定义EventTime字段,在接口中需要实现getRowtimeAttributeDescriptors方法,创建基于EventTime的时间属性信息。
public class UserActionSource implements StreamTableSource<Row>, DefinedRowtimeAttributes {
@Override public TypeInformation<Row> getReturnType() { String[] names = new String[] {"user_name", "data", "user_action_time"}; TypeInformation[] types = new TypeInformation[] {Types.STRING(), Types.STRING(), Types.LONG()}; return Types.ROW(names, types); }
@Override public DataStream<Row> getDataStream(StreamExecutionEnvironment execEnv) {
DataStream<Row> stream = inputStream.assignTimestampsAndWatermarks(...); return stream; }
@Override public List<RowtimeAttributeDescriptor> getRowtimeAttributeDescriptors() { RowtimeAttributeDescriptor rowtimeAttrDescr = new RowtimeAttributeDescriptor( "user_action_time", new ExistingField("user_action_time"), new AscendingTimestamps()); List<RowtimeAttributeDescriptor> listRowtimeAttrDescr = Collections.singletonList(rowtimeAttrDescr); return listRowtimeAttrDescr; } }
tEnv.registerTableSource("user_actions", new UserActionSource());
WindowedTable windowedTable = tEnv .from("user_actions") .window(Tumble.over("10.minutes").on("user_action_time").as("userActionWindow"));
|
小结
本文主要介绍了如何在Flink Table API和SQL中使用时间语义,可以使用两种时间语义:处理时间和事件时间。分别对每种的时间语义的使用方式进行了详细解释。
公众号『大数据技术与数仓』,回复『资料』领取大数据资料包