Flink学习之路(十三)—— Table API 和 Flink SQL (四)—— 窗口(Windows)

时间语义,要配合窗口操作才能发挥作用。最主要的用途,当然就是开窗口、根据时间段做计算了。下面我们就来看看 Table API 和 SQL 中,怎么利用时间字段做窗口操作。
在 Table API 和 SQL 中,主要有两种窗口:Group Windows 和 Over Windows

分组窗口(Group Windows)

分组窗口(Group Windows)会根据时间或行计数间隔,将行聚合到有限的组(Group)中,并对每个组的数据执行一次聚合函数。
Table API 中的 Group Windows 都是使用.window(w:GroupWindow)子句定义的,并且必须由 as 子句指定一个别名。为了按窗口对表进行分组,窗口的别名必须在 group by 子句中,像常规的分组字段一样引用

Table table = input
 .window([w: GroupWindow] as "w") // 定义窗口,别名 w
 .groupBy("w, a") // 以属性 a 和窗口 w 作为分组的 key
 .select("a, b.sum") // 聚合字段 b 的值,求和

或者,还可以把窗口的相关信息,作为字段添加到结果表中:

Table table = input
 .window([w: GroupWindow] as "w") 
 .groupBy("w, a") 
 .select("a, w.start, w.end, w.rowtime, b.count")

Table API 提供了一组具有特定语义的预定义 Window 类,这些类会被转换为底层DataStream 或 DataSet 的窗口操作。
Table API 支持的窗口定义,和我们熟悉的一样,主要也是三种:滚动(Tumbling)、滑动(Sliding)和会话(Session)。

滚动窗口

滚动窗口(Tumbling windows)要用 Tumble 类来定义,另外还有三个方法:

  • over:定义窗口长度
  • on:用来分组(按时间间隔)或者排序(按行数)的时间字段
  • as:别名,必须出现在后面的 groupBy 中

代码如下:

// Tumbling Event-time Window
.window(Tumble.over("10.minutes").on("rowtime").as("w"))
// Tumbling Processing-time Window
.window(Tumble.over("10.minutes").on("proctime").as("w"))
// Tumbling Row-count Window
.window(Tumble.over("10.rows").on("proctime").as("w"))

滑动窗口

滑动窗口(Sliding windows)要用 Slide 类来定义,另外还有四个方法:

  • over:定义窗口长度
  • every:定义滑动步长
  • on:用来分组(按时间间隔)或者排序(按行数)的时间字段
  • as:别名,必须出现在后面的 groupBy 中

代码如下:

// 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"))

会话窗口

会话窗口(Session windows)要用 Session 类来定义,另外还有三个方法:

  • withGap:会话时间间隔
  • on:用来分组(按时间间隔)或者排序(按行数)的时间字段
  • as:别名,必须出现在后面的 groupBy 中

代码如下:

// 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"))

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 window是使用常量指定的。也就是说,时间间隔要指定UNBOUNDED_RANGE,
或者行计数间隔要指定 UNBOUNDED_ROW。而有界的 over window 是用间隔的大小指定的。
实际代码应用如下 :

1) 无界的 over window

// 无界的事件时间 over window
.window(Over.partitionBy("a").orderBy("rowtime").preceding.(UNBOUNDED_RANGE).as("w"))
// 无界的处理时间 over window
.window(Over.partitionBy("a").orderBy("proctime").preceding.(UNBOUNDED_RANGE).as("w"))
// 无界的事件时间 Row-count over window
.window(Over.partitionBy("a").orderBy("rowtime").preceding.(UNBOUNDED_ROW).as("w"))
//无界的处理时间 Row-count over window
.window(Over.partitionBy("a").orderBy("proctime").preceding.(UNBOUNDED_ROW).as("w"))

2) 有界的 over window

// 有界的事件时间 over window
.window(Over.partitionBy("a").orderBy("rowtime").preceding("1.minutes").as("w"))
 
// 有界的处理时间 over window
.window(Over.partitionBy("a").orderBy("proctime").preceding("1.minutes").as("w"))
 
// 有界的事件时间 Row-count over window
.window(Over.partitionBy("a").orderBy("rowtime").preceding("10.rows").as("w"))
 
// 有界的处理时间 Row-count over window
.window(Over.partitionBy("a").orderBy("procime").preceding("10.rows").as("w"))

SQL 中窗口的定义

我们已经了解了在 Table API 里 window 的调用方式,同样,我们也可以在 SQL 中直接加入窗口的定义和使用。

Group Windows

Group Windows 在 SQL 查询的 Group BY 子句中定义。与使用常规 GROUP BY 子句的查询
一样,使用 GROUP BY 子句的查询会计算每个组的单个结果行。
SQL 支持以下 Group 窗口函数:

  • TUMBLE(time_attr, interval)
    定义一个滚动窗口,第一个参数是时间字段,第二个参数是窗口长度。
  • HOP(time_attr, interval, interval)
    定义一个滑动窗口,第一个参数是时间字段,第二个参数是窗口滑动步长,第三个是窗口长度。
  • SESSION(time_attr, interval)
    定义一个会话窗口,第一个参数是时间字段,第二个参数是窗口间隔(Gap)。
    另外还有一些辅助函数,可以用来选择 Group Window 的开始和结束时间戳,以及时间属性。
    这里只写 TUMBLE_,滑动和会话窗口是类似的(HOP_,SESSION_*)。
  • TUMBLE_START(time_attr, interval)
  • TUMBLE_END(time_attr, interval)
  • TUMBLE_ROWTIME(time_attr, interval)
  • TUMBLE_PROCTIME(time_attr, interval)

Over Windows

由于 Over 本来就是 SQL 内置支持的语法,所以这在 SQL 中属于基本的聚合操作。所有聚合必须在同一窗口上定义,也就是说,必须是相同的分区、排序和范围。目前仅支持在当前行范围之前的窗口(无边界和有边界)。

注意,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)

代码练习(以分组滚动窗口为例)

我们可以综合学习过的内容,用一段完整的代码实现一个具体的需求。例如,可以开一个滚动窗口,统计 10 秒内出现的每个 sensor 的个数。

代码如下:

package com.dahuan.tables;

import com.dahuan.bean.SensorReading;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStream;
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.GroupWindow;
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;

public class Table_GroupBy_Window {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        //指定事件时间
        env.setStreamTimeCharacteristic( TimeCharacteristic.EventTime);

        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        // 2. 读入文件数据,得到DataStream
        DataStream<String> inputStream = env.readTextFile("E:\\Project\\FlinkTutorials\\Flink-Scala\\src\\main\\resources\\sensor.txt");

        // 3. 转换成POJO
        DataStream<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 element) {
                        return element.getTimestamp() * 1000L;
                    }
                });

        // 4. 将流转换成表,定义时间特性
        Table dataTable = tableEnv.fromDataStream(dataStream, "id, timestamp as ts, temperature as temp, rt.rowtime");

        // 5.窗口操作 (Group by)
        // 5.1 table API
        //TODO 步长10秒 (定义了十秒内时间下的滚动窗口)
        Table tableAPI = dataTable.window( Tumble.over( "10.seconds" ).on( "rt" ).as( "tw" ) )
                .groupBy( "id,tw" ) //TODO  以属性 id 和窗口 tw 作为分组的 key
                .select( "id,id.count,temp.avg,tw.end" );

        //5.2 SQL
        //TODO 创建临时表
        tableEnv.createTemporaryView( "sensor", dataTable);
        //TODO 定义一个滚动窗口,第一个参数是时间字段,第二个参数是窗口长度。
        String sql = "select id,count(id) as cnt,avg(temp) as avgTemp,tumble_end(rt,interval '10' second)" +
                     "from sensor group by id , tumble(rt,interval '10' second) ";

        Table sqlQuery = tableEnv.sqlQuery( sql );

        tableEnv.toAppendStream( tableAPI, Row.class ).print("tableAPI");
        tableEnv.toAppendStream( sqlQuery,Row.class ).print("SQL");



        env.execute("Table_GroupBy_Window");
    }
}

-END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值