Flink系列Table API和SQL之:时间属性

Flink系列Table API和SQL之:时间属性

  • 一、时间属性和窗口
  • 二、事件时间
    • 1.在创建表的DDL中定义
    • 2.在数据流转换为表时定义
  • 三、处理时间
    • 1.在创建表的DDL中定义
    • 2.在数据流转换为表时定义

一、时间属性和窗口

  • 基于时间的操作(比如时间窗口),需要定义相关的时间语义和时间数据来源的信息。在Table API和SQL中,会给表单独提供一个逻辑上的时间字段,专门用来在表处理程序中指示时间。
  • 所谓的时间属性(time attributes),就是每个表模式结构(schema)的一部分。可以在创建表的DDL里直接定义一个字段,也可以在DataStream转换成表时定义。一旦定义了时间属性,它就可以作为一个普通字段引用,并且可以在基于时间的操作中使用。
  • 时间属性的数据类型为TIMESTAMP,它的行为类似于常规时间戳,可以直接访问并且进行计算。
  • 按照时间语义的不同,可以把时间属性的定义分成事件时间(event time)和处理时间(processing time)两种情况。

二、事件时间

  • 在实际应用中,最常用的就是事件时间。在事件时间语义下,允许表处理程序根据每个数据中包含的时间戳(也就是事件发生的时间)来生成结果。
  • 事件时间语义最大的用途就是处理乱序事件或者延迟事件的场景。通过设置水位线(watermark)来表示事件时间的进展,而水位线可以根据数据的最大事件戳设置一个延迟时间。这样即使在出现乱序的情况下,对数据的处理也可以获得正确的结果。
  • 为了处理无序时间,并区分流中的迟到事件。Flink需要从事件数据中提取时间戳,并生成水位线,用来推进事件时间的进展。
  • 事件时间属性可以在创建表DDL中定义,也可以在数据流和表的转换中定义。

1.在创建表的DDL中定义

  • 在创建表的DDL(CREATE TABLE语句)中,可以增加一个字段,通过WATERMARK语句来定义事件时间属性。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
  • 这里的数值必须用单引号引起来,而单位用SECOND和SECONDS是等效的。
  • Flink中支持的事件时间属性数据类型必须为TIMESTAMP或者TIMESTAMP_LTZ
  • 这里的TIMESTAMP_LTZ是指带有本地时区信息的时间戳(TIMESTAMP WITH LOCAL TIMEZONE)
  • 一般情况下如果数据中的时间戳是"年-月-日-时-分-秒"的形式,那就是不带时区信息的,可以将事件时间属性定义为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秒的水位线延迟
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        // 1. 在创建表的DDL中直接定义时间属性
        String createDDL = "CREATE TABLE clickTable (" +
                " user_name STRING, " +
                " url STRING, " +
                " ts BIGINT, " +
                " et AS TO_TIMESTAMP( FROM_UNIXTIME(ts / 1000) ), " +
                " WATERMARK FOR et AS et - INTERVAL '1' SECOND " +
                ") WITH (" +
                " 'connector' = 'filesystem', " +
                " 'path' = '/Users/fei.yang4/project/learn/src/main/java/com/bigdata/plus/flink/input/clicks.csv', " +
                " 'format' =  'csv' " +
                ")";

        tableEnv.executeSql(createDDL);

2.在数据流转换为表时定义

  • 事件时间属性也可以在将DataStream转换为表的时候来定义,调用fromDataStream()方法创建表时,可以追加参数来定义表中的字段结构。这时可以给某个字段加上.rowtime()后缀,就表示将当前字段指定为事件时间属性。这个字段可以是数据中本不存在、额外追加上去的逻辑字段,也可以是本身固有的字段,那么这个字段就会被事件时间属性所覆盖,类型也会被转换为TIMESTAMP。不论哪种方式,时间属性字段中保存的都是事件的时间戳(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());
        // 2. 在流转换成Table时定义时间属性
        SingleOutputStreamOperator<Event> clickStream = env.addSource(new ClickSource())
                .assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ZERO)
                        .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                            @Override
                            public long extractTimestamp(Event event, long l) {
                                return event.timestamp;
                            }
                        }));

        Table clickTable = tableEnv.fromDataStream(clickStream, $("user"), $("url"), $("timestamp").as("ts"),
                $("et").rowtime());
        clickTable.printSchema();
(
  `user` STRING,
  `url` STRING,
  `ts` BIGINT,
  `et` TIMESTAMP(3) *ROWTIME*
)

三、处理时间

相比之下,处理时间就比较简单了,就是我们的系统时间,使用时不需要提取时间戳(timestamp)和生成水位线(watermark)。因此在定义处理时间属性时,必须要额外声明一个字段,专门用来保存当前的处理时间。

类似地,处理时间属性的定义也有两种方式:创建表DDL中定义,或者在数据流转换成表时定义。

1.在创建表的DDL中定义

在创建表的DDL(CREATE TABLE语句中),可以增加一个额外的字段,通过调用系统内置的PROCTIME()函数来指定当前的处理时间属性,返回的类型是TIMESTAMP_LTZ。

CREATE TABLE EventTable(
	user STRING,
	url STRING,
	ts AS PROCTIME()
) WITH (
	...
);

这里的时间属性,其实是以计算列(computed column)的形式定义出来的。所谓的计算列是Flink SQL中引入的特殊概念,可以用一个AS语句来在表中产生数据中不存在的列,并且可以利用原有的列、各种运算符及内置函数。
在前面事件时间属性的定义中,将ts字段转换成TIMESTAMP_LTZ类型的ts_ltz,也是计算列的定义方式。

2.在数据流转换为表时定义

处理时间属性同样可以在将DataStream转换为表的时候来定义。调用fromDataStream()方法创建表时,可以用.proctime()后缀来指定处理时间属性字段。由于处理时间是系统时间,原始数据中并没有这个字段,所以处理时间属性一定不能定义在一个已有字段上,只能定义在表结构所有字段的最后,作为额外的逻辑字段出现。

代码中定义处理时间属性的方法如下:

DataStream<Tuple2<String,String>> stream = ...;

声明一个额外的字段作为处理时间属性字段

Table table = tEnv.fromDataStream(stream,$("user"),$("url"),$("ts").proctime());

你可能感兴趣的:(Flink,Flink系列,Table,API和SQL,时间属性)