Flink 时间特性及sql窗口
文章目录
一、概述
- 基于时间的操作(比如 Table API 和 SQL 中窗口操作),需要定义相关的时间语义和时间数据来源的信息
- Table 可以提供一个逻辑上的时间字段,用于在表处理程序中,指示时间和访问相应的时间戳
- 时间属性,可以是每个表schema的一部分。一旦定义了时间属性,它就可以作为一个字段引用,并且可以在基于时间的操作中使用
- 时间属性的行为类似于常规时间戳,可以访问,并且进行计算
二、定义处理时间(Processing Time)
处理时间语义下,允许表处理程序根据机器的本地时间生成结果。它是时间的最简单概念。它既不需要提取时间戳,也不需要生成 watermark。
由三种方式可以生成proctime:
- 由DataSream转换成表时
- 定义Table Schema时
- 创建表的DDL时
2.1 由DataSream转换成表时
在定义Table Schema期间,可以使用.proctime,指定字段名义处理时间字段。
proctime属性只能通过附加逻辑字段,来扩展物理schema,因此,只能在schema定义的末尾来定义它。
Table dataTable = tableEnv.fromDataStream(dataStream, "id, timestamp as ts, temperature as temp, **pt.proctime**");
2.2 定义Table Schema时
.withSchema(new Schema()
.field("id", DataTypes.STRING())
.field("timestamp",DataTypes.BIGINT())
.field("temperature",DataTypes.DOUBLE())
.**field("pt",DataTypes.TIMESTAMP(3))**
.**proctime()**
)
2.3 创建表的DDL中
String sinkDDL =
"create table dataTable (" +
" id varchar(20) not null, " +
" ts bigint, " +
" temperature double, " +
" pt AS PROCTIME() " +
" ) with (" +
" 'connector.type' = 'filesystem', " +
" 'connector.path' = '/sensor.txt', " +
" 'format.type' = 'csv')";
tableEnv.sqlUpdate(sinkDDL);
测试代码
package com.root.table;
import com.root.SensorReading;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.java.StreamTableEnvironment;
import org.apache.flink.types.Row;
/**
* @author Kewei
* @Date 2022/3/8 9:37
*/
public class TableTest7_Time {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
DataStreamSource<String> inputStream = env.readTextFile("data/sensor.txt");
SingleOutputStreamOperator<SensorReading> dataStream = inputStream.map(line -> {
String[] fields = line.split(",");
return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
})
// DataStream转换为Table,设置当前运行时间pt.proctime
Table dataTable = tableEnv.fromDataStream(dataStream, "id, timestamp as ts, temperature as temp, pt.proctime");
dataTable.printSchema();
tableEnv.toAppendStream(dataTable, Row.class).print();
env.execute();
}
}
三、定义事件时间(Event Time)
- 事件时间语义,允许表处理程序根据每个记录中包含的时间生成结果。这样即使在有乱序事件或者延迟事件时,也可以获得正确的结果。
- 为了处理无序事件,并区分流中的准时和迟到事件;Flink需要从事件数据中,提取时间戳,并用来推送事件时间的进展
- 定义事件事件,同样有三种方法:
- 由DataStream转换成表时指定
- 定义Table Schema时指定
- 在创建表的DDL中定义
3.1 由DataStream转换成表时
- 由DataStream转换成表时,指定时间(推荐)
- 在DataStream转换成Table,使用
.rowtime可以定义事件事件属性 - 注意需要先指定好事件时间,打开使用事件时间的设置
Table dataTable1 = tableEnv.fromDataStream(dataStream, "id, timestamp.rowtime, temperature");
Table dataTable2 = tableEnv.fromDataStream(dataStream, "id, timestamp, temperature,rt.rowtime");
// rowtime的位置不受限制
3.2 定义Table Schema时
.withSchema(new Schema()
.field("id", DataTypes.STRING())
.field("timestamp",DataTypes.BIGINT())
.rowtime(
new Rowtime()
.timestampsFromField("timestamp") // 从字段中提取时间戳
.watermarksPeriodicBounded(1000) // watermark延迟1秒
)
.field("temperature",DataTypes.DOUBLE())
)
3.3 创建表的DDL中
String sinkDDL =
"create table dataTable (" +
" id varchar(20) not null, " +
" ts bigint, " +
" temperature double, " +
" rt AS TO_TIMESTAMP( FROM_UNIXTIME(ts) ), " +
" watermark for rt as rt - interval '1' second"
" ) with (" +
" 'connector.type' = 'filesystem', " +
" 'connector.path' = '/sensor.txt', " +
" 'format.type' = 'csv')";
tableEnv.sqlUpdate(sinkDDL);
测试代码
package com.root.table;
import com.root.SensorReading;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.java.StreamTableEnvironment;
import org.apache.flink.types.Row;
/**
* @author Kewei
* @Date 2022/3/8 9:37
*/
public class TableTest7_Time {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
env.setParallelism(1);
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
DataStreamSource<String> inputStream = env.readTextFile("data/sensor.txt");
SingleOutputStreamOperator<SensorReading> dataStream = inputStream.map(line -> {
String[] fields = line.split(",");
return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
})
.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<SensorReading>(Time.seconds(2)) {
@Override
public long extractTimestamp(SensorReading value) {
return value.getTimestamp()*1000L;
}
});
// DataStream转换为Table,设置当前时间pt.proctime
Table dataTable = tableEnv.fromDataStream(dataStream, "id, timestamp as ts, temperature as temp, pt.proctime");
// 使用.rowtime时,需要设置dataStream的事件时间assignTimestampsAndWatermarks
Table dataTable1 = tableEnv.fromDataStream(dataStream, "id, **timestamp.rowtime**, temperature");
Table dataTable2 = tableEnv.fromDataStream(dataStream, "id, timestamp, temperature,**rt.rowtime**");
dataTable1.printSchema();
dataTable2.printSchema();
// dataTable.printSchema();
// tableEnv.toAppendStream(dataTable, Row.class).print();
tableEnv.toAppendStream(dataTable2,Row.class).print();
env.execute();
}
}
四、窗口
相关博客:
Flink-分组窗口 | Over Windows | SQL 中的 Group Windows | SQL 中的 Over Windows
- 时间语义,要配合窗口操作才能发挥作用。
- 在Table API和SQL中,主要有两种窗口
- Group Windows(分组窗口)
- 根据时间戳或行计数间隔,将行聚合到有限的组(Group)中,并对每个组的数据执行一次聚合函数
- Over Windows
- 针对每个输入行,计算相邻行范围内的聚合
- Group Windows(分组窗口)
4.1 Group Windows
Group Windows是使用window(w:GroupWindows)子句定义的,并且必须由as子句指定一个别名。
为了按窗口对表进行分组,窗口的别名必须在group by 子句中,像常规的分组字段一样引用。
Table table = input
.window([w:GroupWindow] as "w") // 定义窗口,别名为w
.groupBy("w, a") // 按照字段 a和窗口 w分组
.select("a,b.sum"); // 聚合
Table API 提供了一组具有特定语义的预定义window类,这些类会被转换为底层DataStream或DataSet的窗口操作。
分组窗口分为三种:
- 滚动窗口
- 滑动窗口
- 会话窗口
滚动窗口(Tumbling windows)要用Tumble来定义
// Tumbling Event-time Window(事件时间字段rowtime)
.window(Tumble.over("10.minutes").on("rowtime").as("w"))
// Tumbling Processing-time Window(处理时间字段proctime)
.window(Tumble.over("10.minutes").on("proctime").as("w"))
// Tumbling Row-count Window (类似于计数窗口,按处理时间排序,10行一组)
.window(Tumble.over("10.rows").on("proctime").as("w"))
- over:定义窗口长度
- on:用来分组(按时间间隔)或排序(按行数)的时间字段
- as:别名,必须出现在后面的groupBy中
滑动窗口(Sliding windows)要用Slide类来定义
// Sliding Event-time Window
.window(Slide.over("10.minutes").every("5.minutes").on("rowtime").as("w"))
// Sliding Processing-time window
.window(Slide.over("10.minutes").every("5.minutes").on("proctime").as("w"))
// Sliding Row-count window
.window(Slide.over("10.rows").every("5.rows").on("proctime").as("w"))
- over:定义窗口长度
- every:定义滑动步长
- on:用来分组(按时间间隔)或者排序(按行数)的时间字段
- as:别名,必须出现在后面的group by中
会话窗口(Session windows)要用Session类来定义
// Session Event-time Window
.window(Session.withGap("10.minutes").on("rowtime").as("w"))
// Session Processing-time Window
.window(Session.withGap("10.minutes").on("proctime").as("w"))
- withGap:会话时间间隔
- on:用来分组(按时间间隔)或者排序(按行数)的时间字段
- as:别名,必须出现在后面的groupBy中
4.2 SQL中的Group Windows
Group Windows定义在SQL查询的Group By子句中
- TUMBLE(time_attr, interval)
- 定义一个滚动窗口,每一个参数是时间字段,第二个参数是窗口长度
- HOP(time_attr, interval, interval)
- 定义一个滑动窗口,第一个参数是时间字段,第二个参数是窗口滑动步长,第三个窗口长度。
- SESSION(time_attr, interval)
- 定义一个会话窗口,第一个参数是时间字段,第二个参数是窗口间隔。
测试代码
package com.root.table;
import com.root.SensorReading;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.Tumble;
import org.apache.flink.table.api.java.StreamTableEnvironment;
import org.apache.flink.types.Row;
/**
* @author Kewei
* @Date 2022/3/8 10:14
*/
public class TableTest8_Window {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
env.setParallelism(1);
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
DataStreamSource<String> inputStream = env.readTextFile("data/sensor.txt");
SingleOutputStreamOperator<SensorReading> dataStream = inputStream.map(line -> {
String[] field = line.split(",");
return new SensorReading(field[0], new Long(field[1]), new Double(field[2]));
})
.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<SensorReading>(Time.seconds(2)) {
@Override
public long extractTimestamp(SensorReading value) {
return value.getTimestamp() * 1000L;
}
});
Table dataTable = tableEnv.fromDataStream(dataStream, "id,timestamp as ts, temperature as tp,rt.rowtime");
tableEnv.createTemporaryView("sensor",dataTable);
// Table表的API
Table resultTable = dataTable.window(Tumble.over("10.seconds").on("rt").as("tw"))
.groupBy("id, tw")
.select("id,id.count,tp.avg,tw.end");
// sql的语法
Table resultSqlTable = tableEnv.sqlQuery("select id, count(id) as cnt, avg(tp) as avgTemp, tumble_end(rt, interval '10' second) " +
"from sensor group by id, tumble(rt, interval '10' second)");
resultTable.printSchema();
resultSqlTable.printSchema();
tableEnv.toAppendStream(resultTable, Row.class).print("result");
tableEnv.toRetractStream(resultSqlTable,Row.class).print("sql");
env.execute();
}
}
4.3 Over Windows
相关博客:
- Over window 聚合是标准 SQL 中已有的(over 子句),可以在查询的 SELECT 子句中定义
- Over window 聚合,会针对每个输入行,计算相邻行范围内的聚合
- Over windows 使用 window(w:overwindows*)子句定义,并在 select()方法中通过别名来引用
Table table = input
.window([w: OverWindow] as "w")
.select("a, b.sum over w, c.min over w");
Table API 提供了 Over 类,来配置 Over 窗口的属性。
无界Over Windows
可以在事件时间或处理时间,以及指定为时间间隔,或行计数的范围内,定义Over Windows
无界的over windows是使用常量指定的
// 无界的事件时间over window (时间字段 "rowtime")
.window(Over.partitionBy("a").orderBy("rowtime").preceding(UNBOUNDED_RANGE).as("w"))
//无界的处理时间over window (时间字段"proctime")
.window(Over.partitionBy("a").orderBy("proctime").preceding(UNBOUNDED_RANGE).as("w"))
// 无界的事件时间Row-count over window (时间字段 "rowtime")
.window(Over.partitionBy("a").orderBy("rowtime").preceding(UNBOUNDED_ROW).as("w"))
//无界的处理时间Row-count over window (时间字段 "rowtime")
.window(Over.partitionBy("a").orderBy("proctime").preceding(UNBOUNDED_ROW).as("w"))
partitionBy是可选项
有界Over Windows
有界的Over Windows使用间隔的大小指定的
// 有界的事件时间over window (时间字段 "rowtime",之前1分钟)
.window(Over.partitionBy("a").orderBy("rowtime").preceding("1.minutes").as("w"))
// 有界的处理时间over window (时间字段 "rowtime",之前1分钟)
.window(Over.partitionBy("a").orderBy("porctime").preceding("1.minutes").as("w"))
// 有界的事件时间Row-count over window (时间字段 "rowtime",之前10行)
.window(Over.partitionBy("a").orderBy("rowtime").preceding("10.rows").as("w"))
// 有界的处理时间Row-count over window (时间字段 "rowtime",之前10行)
.window(Over.partitionBy("a").orderBy("proctime").preceding("10.rows").as("w"))
4.4 SQL中的Over Windows
- 用Over做窗口聚合时, 所有聚合必须在同一窗口上定义,也就是说必须是相同的分区、排序和范围
- 目前仅支持在当前范围之前的窗口
- order by必须在单一的时间属性上指定。
SELECT COUNT(amount) OVER (
PARTITION BY user
ORDER BY proctime
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
FROM Orders
// 也可以做多个聚合
SELECT COUNT(amount) OVER w, SUM(amount) OVER w
FROM Orders
WINDOW w AS (
PARTITION BY user
ORDER BY proctime
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
测试代码
package com.root.table;
import com.root.SensorReading;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.table.api.Over;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.java.StreamTableEnvironment;
import org.apache.flink.types.Row;
/**
* @author Kewei
* @Date 2022/3/8 10:48
*/
public class TableTest9_TimeWindow {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
env.setParallelism(1);
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
DataStreamSource<String> inputStream = env.readTextFile("data/sensor.txt");
SingleOutputStreamOperator<SensorReading> dataStream = inputStream.map(line -> {
String[] fields = line.split(",");
return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
})
.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<SensorReading>(Time.seconds(2)) {
@Override
public long extractTimestamp(SensorReading sensorReading) {
return sensorReading.getTimestamp() * 1000L;
}
});
Table dataTable = tableEnv.fromDataStream(dataStream, "id, timestamp as ts, temperature as temp, rt.rowtime");
tableEnv.createTemporaryView("sensor",dataTable);
Table resultTable = dataTable.window(Over.partitionBy("id").orderBy("rt").preceding("2.rows").as("ow"))
.select("id, rt, id.count over ow, temp.avg over ow");
Table resultSqlTable = tableEnv.sqlQuery("select id, rt, count(id) over ow, avg(temp) over ow" +
" from sensor " +
" window ow as (partition by id order by rt rows between 2 preceding and current row)");
resultTable.printSchema();
resultSqlTable.printSchema();
tableEnv.toAppendStream(resultTable, Row.class).print("result");
tableEnv.toRetractStream(resultSqlTable,Row.class).print("sql");
env.execute();
}
}
1万+



被折叠的 条评论
为什么被折叠?



