Flink第八章:Flink Table API

文章目录


前言

  • 随着大数据实时处理需求的日益增长,Apache Flink 凭借其卓越的流处理能力和有状态计算的精确性,已成为业界事实上的标准。然而,对于习惯于使用 SQL 和关系代数进行数据分析的开发人员与数据分析师而言,直接操作 DataStream API 或 Process Function 不仅代码量大、开发周期长,而且需要深入理解底层状态管理和时间语义,这无疑增加了实时计算的门槛。

  • 为了弥合这一鸿沟,Flink 提供了Table API 与 SQL 这一统一的高级关系型 API。它允许开发者以声明式的方式,通过类似 SQL 的查询语句或直观的链式方法调用,对无界流和有界批数据进行统一处理。Table API 不仅大幅简化了复杂逻辑的编码难度,还天然集成了 Calcite 强大的查询优化能力,能够在保证高性能的同时,让业务逻辑的表达更加清晰、简洁。

  • 本文旨在为您系统性地介绍 Flink Table API 的核心概念与实战开发流程。我们将从基础的环境搭建、表定义与查询入手,逐步深入到与 Kafka、JDBC 等常用外部系统的连接集成,并重点解析流处理中至关重要的窗口聚合操作(包括滚动窗口与滑动窗口)。通过本文的学习,您将能够快速上手 Flink 的 SQL 化开发模式,将实时数据处理任务以更高效、更优雅的方式落地。


一、Table API 是什么

(一)概念

  • Flink 对批处理和流处理,提供了统一的上层API
  • Table API 是一套内嵌在 Java 和 Scala 语言中的查询API,它允许以非常直观的方式组合来自一些关系运算符的查询
  • Flink 的 SQL 支持基于实现了SQL 标准的Apache Calcite

二、开发步骤

(一)添加依赖

java 复制代码
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-planner_2.12</artifactId>
<version>${flink.version}</version>
</dependency>

(二)编写程序

1.创建表环境

对于Flink流处理框架来说,数据流和表在结构上是有所区别的 。所以使用Table API需要一个特别的运行时环境,这就是所谓的 "表环境" (TableEnvironment)。

java 复制代码
// 创建流执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();     
// 创建表环境
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

2.创建表

表(Table)是我们非常熟悉的一个概念,它是关系型数据库中数据存储的基本形式,也是SQL执行的基本对象。

java 复制代码
// 创建流对象
DataStreamSource<String> dataStreamSource = env.socketTextStream("niit01", 7777); 
// 将流对象转变为一个表 
tableEnv.createTemporaryView("wc", dataStreamSource);

3.查询表

创建好了表,接下来自然就是对表进行查询转换了。对一个表的查询(Query)操作,就对应着**流数据的转换(Transform)**处理。

java 复制代码
Table table = tableEnv.sqlQuery("select * from wc");

4.输出表

  • 表的创建和查询,就对应着流处理中的读取数据源(Source)和转换(Transform);而最后一个步骤Sink,也就是将结果数据输出到外部系统,就对应着表的输出操作。
  • 在代码上,输出一张表最直接的方法,就是调用Table的方法executeInsert()方法将一个 Table写入到注册过的表中,方法传入的参数就是注册的表名。
java 复制代码
// 注册表,用于输出数据到外部系统
tableEnv.executeSql("CREATE TABLE OutputTable ... WITH ( 'connector' = ... )");
// 经过查询转换,得到结果表
Table result = ...// 
将结果表写入已注册的输出表中
result.executeInsert("OutputTable");
  • 在底层,表的输出是通过将数据写入到TableSink来实现的 。TableSink是Table API中提供的一个向外部系统写入数据的通用接口 ,可以支持不同的文件格式(比如CSV、Parquet)、存储数据库(比如JDBC、Elasticsearch)和消息队列(比如Kafka)。

5.表和流的转换

(1)将流(DataStream)转换成表(Table)
fromDataStream()方法
  • 调用fromDataStream()方法可以将一个DataStream转换成表,调用表环境的fromDataStream()方法,返回的就是一个Table对象。
java 复制代码
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 获取表环境
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
// 读取数据源
SingleOutputStreamOperator<WaterSensor> sensorDS = env.fromSource(...)
// 将数据流转换成表
Table sensorTable = tableEnv.fromDataStream(sensorDS);
  • 如果流中的数据本身就是定义好的POJO类型,如:WaterSensor,将流转换成表之后,每一行数据就对应着一个WaterSensor,而表中的列名就对应着WaterSensor中的属性。
  • 另外,我们还可以在fromDataStream()方法中增加参数,用来指定提取哪些属性作为表中的字段名,并可以任意指定位置:
java 复制代码
// 提取数据中的id和vc作为表中的列
Table sensorTable = tableEnv.fromDataStream(sensorDS, $("id"), $("vc"));
也可以通过表达式的as()方法对字段进行重命名:
// 将id字段重命名为sid
Table sensorTable = tableEnv.fromDataStream(sensorDS, $("id").as("sid"), $("vc"));
调用createTemporaryView()方法
  • 调用fromDataStream()方法简单直观,可以直接实现DataStream到Table的转换;不过如果我们希望直接在SQL中引用这张表,就需要调用表环境的createTemporaryView()方法来创建虚拟视图了。
  • 我们可以直接调用createTemporaryView()方法创建虚拟表 ,传入的两个参数,第一个依然是注册的表名,而第二个可以直接就是DataStream。之后仍旧可以传入多个参数,用来指定表中的字段
java 复制代码
tableEnv.createTemporaryView("sensorTable",sensorDS, $("id"),$("ts"),$("vc"));

这样,我们接下来就可以直接在SQL中引用表sensorTable了。

(2)将表(Table)转换成流(DataStream)
调用toDataStream()方法

将一个Table对象转换成DataStream非常简单,直接调用表环境的方法toDataStream()就可以了。例如:

java 复制代码
tableEnv.toDataStream(table).print();
调用toChangelogStream()方法
  • 表中进行了分组聚合 统计,所以表中的每一行是会"更新"的。对于这样有更新操作的表,我们不应该直接把它转换成DataStream打印输出,而是记录一下它的"更新日志"(change log)。这样一来,对于表的所有更新操作,就变成了一条更新日志的流,我们就可以转换成流打印输出了。
  • 代码中需要调用的是表环境的toChangelogStream()方法:
java 复制代码
Table table = tableEnv.sqlQuery(
"SELECT id, sum(vc) " +
"FROM source " +
"GROUP BY id" 
);
// 将表转换成更新日志流
tableEnv.toChangelogStream(table).print();
(3)支持的数据类型

整体来看,DataStream中支持的数据类型,Table中也是都支持的,只不过在进行转换时需要注意一些细节。

原子类型
  • 在Flink中,基础数据类型Integer、Double、String)和通用数据类型(也就是不可再拆分的数据类型)统一称作"原子类型" 。原子类型的DataStream,转换之后就成了只有一列的Table,列字段(field)的数据类型可以由原子类型推断出。另外,还可以在fromDataStream()方法里增加参数,用来重新命名列字段。
java 复制代码
StreamTableEnvironment tableEnv = ...;
DataStream<Long> stream = ...;
// 将数据流转换成动态表,动态表只有一个字段,重命名为myLong
Table table = tableEnv.fromDataStream(stream, $("myLong"));
Tuple类型
  • 原子类型不做重命名时,默认的字段名就是"f0",其实就是将原子类型看作了一元组Tuple1的处理结果。
  • Table支持Flink中定义的元组类型Tuple,对应在表中字段名默认就是元组中元素的属性名f0、f1、f2...。所有字段都可以被重新排序,也可以提取其中的一部分字段。字段还可以通过调用表达式的as()方法来进行重命名。
java 复制代码
StreamTableEnvironment tableEnv = ...;
DataStream<Tuple2<Long, Integer>> stream = ...;
// 将数据流转换成只包含f1字段的表
Table table = tableEnv.fromDataStream(stream, $("f1"));
// 将数据流转换成包含f0和f1字段的表,在表中f0和f1位置交换
Table table = tableEnv.fromDataStream(stream, $("f1"), $("f0"));
// 将f1字段命名为myInt,f0命名为myLong
Table table = tableEnv.fromDataStream(stream,$("f1").as("myInt"),$("f0").as("myLong"));
POJO 类型
  • Flink也支持多种数据类型组合成的"复合类型" ,最典型的就是简单Java对象(POJO 类型)。由于POJO中已经定义好了可读性强的字段名,这种类型的数据流转换成Table就直接使用属性名作为表的字段名。
  • 将POJO类型的DataStream转换成Table,如果不指定字段名称,就会直接使用原始 POJO 类型中的字段名称。POJO中的字段同样可以被重新排序、提却和重命名。
java 复制代码
StreamTableEnvironment tableEnv = ...;
DataStream<Event> stream = ...;
Table table = tableEnv.fromDataStream(stream);
Table table = tableEnv.fromDataStream(stream, $("user"));
Table table = tableEnv.fromDataStream(stream, $("user").as("myUser"), $("url").as("myUrl"));
Row类型
  • Flink中还定义了一个在关系型表中更加通用的数据类型------行(Row),它是Table中数据的基本组织形式。

三、connect连接

  • Flink的Table API和SQL程序可以连接到其他外部系统,用于读写批处理表和流处理表。
  • Table Source提供对存储在外部系统(如数据库、键值存储、消息队列或文件系统)中的数据的访问。
  • Table Sink将表发送到外部存储系统。

connectdemo代码示例

java 复制代码
package chapter08;

import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.TableResult;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;

public class ConnectorDemo {
    public static void main(String[] args) throws Exception {
        // 0. 创建流执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        // 1. 创建表环境
        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        // 2. 创建表: table source
        tableEnv.executeSql("CREATE TABLE source (" +
                "   id INT, " +
                "   ts BIGINT, " +
                "   vc INT" +
                ") WITH(" +
                "   'connector' = 'datagen', " +
                "   'rows-per-second' = '1', " +
                "   'fields.id.kind' = 'random', " +
                "   'fields.id.min' = '1', " +
                "   'fields.id.max' = '10', " +
                "   'fields.ts.kind' = 'sequence', " +
                "   'fields.ts.start' = '1', " +
                "   'fields.ts.end' = '1000000', " +
                "   'fields.vc.kind' = 'random', " +
                "   'fields.vc.min' = '1', " +
                "   'fields.vc.max' = '100'" +
                ")");

//        TableResult tableResult = tableEnv.executeSql("select * from source");
//        tableResult.print();

        // 3. 创建表:table sink
        tableEnv.executeSql("CREATE TABLE sink(" +
                "   id INT, " +
                "   sumVC INT " +
                ") WITH (" +
                "   'connector' = 'print' " +
                ")");

        tableEnv.executeSql("insert into sink select id, sum(vc) from source group by id");

        // env.execute();
    }
}

(一)案例一:Kafka Connector

  • 需求:从Kafka的topic1中消费数据并过滤出状态为success的数据再写入到Kafka的topic2
  • 数据
    {"user_id": "1", "page_id":"1", "status": "success"}
    {"user_id": "1", "page_id":"1", "status": "success"}
    {"user_id": "1", "page_id":"1", "status": "success"}
    {"user_id": "1", "page_id":"1", "status": "success"}
    {"user_id": "1", "page_id":"1", "status": "fail"}
java 复制代码
package chapter08;

import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;

public class KafkaConnector {
    public static void main(String[] args) {
        // 0. 创建流执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        // 1. 创建表环境
        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        // 2. 创建表table1:table source
        tableEnv.executeSql("CREATE TABLE table1 (\n" +
                "  `user_id` int,\n" +
                "  `page_id` int,\n" +
                "  `status` STRING\n" +
                ") WITH (\n" +
                "  'connector' = 'kafka',\n" +
                "  'topic' = 'topic1',\n" +
                "  'properties.bootstrap.servers' = 'hadoop102:9092',\n" +
                "  'properties.group.id' = 'g1',\n" +
                "  'scan.startup.mode' = 'latest-offset',\n" +
                "  'format' = 'json'\n" +
                ")\n");

        // 3. 创建表table2: table sink
        tableEnv.executeSql("CREATE TABLE table2 (\n" +
                "  `user_id` int,\n" +
                "  `page_id` int,\n" +
                "  `status` STRING\n" +
                ") WITH (\n" +
                "  'connector' = 'kafka',\n" +
                "  'topic' = 'topic2',\n" +
                "  'properties.bootstrap.servers' = 'hadoop102:9092',\n" +
                "  'properties.group.id' = 'g1',\n" +
                "  'scan.startup.mode' = 'latest-offset',\n" +
                "  'format' = 'json'\n" +
                ")\n");

        // 4. 执行语句
        tableEnv.executeSql("insert into table2 select * from table1 where status = 'success'");
    }
}

(二)JDBC Connector案例2

  • 需求:FlinkSQL 读取 mysql 数据,并将数据打印到控制台
  • 数据:
    {"user_id": "1", "page_id":"1", "status": "success"}
    {"user_id": "1", "page_id":"1", "status": "success"}
    {"user_id": "1", "page_id":"1", "status": "success"}
    {"user_id": "1", "page_id":"1", "status": "success"}
    {"user_id": "1", "page_id":"1", "status": "fail"}
java 复制代码
package chapter08;

import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;

public class JDBCConnectorDemo1 {
    public static void main(String[] args) {
        // 0. 创建流执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        // 1. 创建表环境
        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        // 2. 创建表table1:table source
        tableEnv.executeSql("CREATE TABLE table1 (\n" +
                "  `user_id` int,\n" +
                "  `page_id` int,\n" +
                "  `status` STRING\n" +
                ") WITH (\n" +
                "  'connector' = 'kafka',\n" +
                "  'topic' = 'topic1',\n" +
                "  'properties.bootstrap.servers' = 'hadoop102:9092',\n" +
                "  'properties.group.id' = 'g1',\n" +
                "  'scan.startup.mode' = 'latest-offset',\n" +
                "  'format' = 'json'\n" +
                ")\n");

        // 3. 创建表table2: table sink
        tableEnv.executeSql("CREATE TABLE table2 (\n" +
                "  `user_id` int,\n" +
                "  `page_id` int,\n" +
                "  `status` STRING\n" +
                ") WITH (\n" +
                "   'connector' = 'jdbc',\n" +
                "   'url' = 'jdbc:mysql://hadoop102:3306/flink',\n" +
                "   'table-name' = 't_success',\n" +
                "   'username' = 'root',\n" +
                "   'password' = '000000'\n" +
                ")\n");

        // 4. 执行SQL语句
        tableEnv.executeSql("insert into table2 select * from table1 where status='success'\n");
    }
}

四、Table API 的窗口

  • Flink 提供丰富的窗口操作,用于对无限流数据按时间或计数切分成有限"桶"进行聚合。
  • 在 Table API / SQL 中,窗口通过 表值函数(TVF) 实现,语法更符合 SQL 标准。

(一)滚动窗口 Tumble

特点:固定大小、不重叠,每个事件只属于一个窗口。

常用场景:每分钟统计、每小时汇总等。

1.案例一:事件时间

  • 测试数据:
json 复制代码
{"username":"zs","price":20,"event_time":"2025-07-17 10:10:10"}
{"username":"zs","price":15,"event_time":"2025-07-17 10:10:30"}
{"username":"zs","price":20,"event_time":"2025-07-17 10:10:40"}
{"username":"zs","price":20,"event_time":"2025-07-17 10:11:03"}
{"username":"zs","price":20,"event_time":"2025-07-17 10:11:04"}
{"username":"zs","price":20,"event_time":"2025-07-17 10:12:04"}
{"username":"zs","price":20,"event_time":"2025-07-17 11:12:04"}
{"username":"zs","price":20,"event_time":"2025-07-17 11:12:04"}
{"username":"zs","price":20,"event_time":"2025-07-17 12:12:04"}
{"username":"zs","price":20,"event_time":"2025-07-18 12:12:04"}
  • 需求一:每隔1分钟统计这1分钟的每个用户的总消费金额和消费次数
java 复制代码
package tableapi;

import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;

public class TumbleEventTimeDemo {
    public static void main(String[] args) throws Exception {
        // 1. 执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);          // 便于观察输出

        StreamTableEnvironment tEnv = StreamTableEnvironment.create(env);

        // 2. 定义源表(Kafka JSON 格式,带事件时间和水印)
        tEnv.executeSql(
            "CREATE TABLE source_table (\n" +
            "  username STRING,\n" +
            "  price INT,\n" +
            "  event_time TIMESTAMP(3),                      -- 事件时间字段\n" +
            "  WATERMARK FOR event_time AS event_time - INTERVAL '3' SECOND   -- 允许3秒乱序\n" +
            ") WITH (\n" +
            "  'connector' = 'kafka',\n" +
            "  'topic' = 'topic1',\n" +
            "  'properties.bootstrap.servers' = 'hadoop102:9092',\n" +
            "  'properties.group.id' = 'g1',\n" +
            "  'scan.startup.mode' = 'latest-offset',        -- 只读新数据;测试时可改为 'earliest-offset'\n" +
            "  'format' = 'json',\n" +
            "  'json.timestamp-format.standard' = 'SQL'      -- 明确指定时间戳格式(可选)\n" +
            ")"
        );

        // 3. 滚动窗口查询:窗口大小 1 分钟(60 秒)
        tEnv.executeSql(
            "SELECT\n" +
            "  window_start,\n" +
            "  window_end,\n" +
            "  username,\n" +
            "  COUNT(1) AS zongNum,\n" +
            "  SUM(price) AS totalMoney\n" +
            "FROM TABLE(\n" +
            "  TUMBLE(TABLE source_table, DESCRIPTOR(event_time), INTERVAL '60' SECOND)\n" +
            ")\n" +
            "GROUP BY window_start, window_end, username"
        ).print();

        // env.execute("Tumble Window Job");  // 如果使用 print() 则不需要,否则需触发
    }
}
  • 水印策略:WATERMARK FOR event_time AS event_time - INTERVAL '3' SECOND,容忍 3 秒延迟。

  • 窗口函数:TUMBLE(TABLE 表名, DESCRIPTOR(时间字段), 窗口大小)

  • 窗口属性:window_start 和 window_end 自动生成,可出现在 SELECT 和 GROUP BY 中。

(二)滑动窗口 HOP

特点:固定大小、可重叠,每个事件可能属于多个窗口。

常用场景:每隔 10 秒统计过去 1 分钟的数据

2.案例二:处理时间

测试数据:

json 复制代码
{"username":"zs","price":20}
{"username":"lisi","price":15}
{"username":"lisi","price":20}
{"username":"zs","price":20}
{"username":"zs","price":20}
{"username":"zs","price":20}
{"username":"zs","price":20}

使用 处理时间(Processing Time),窗口大小 1 分钟,滑动步长 10 秒,统计每个用户的总金额和次数。

java 复制代码
package tableapi;

import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;

public class HopProcessingTimeDemo {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        StreamTableEnvironment tEnv = StreamTableEnvironment.create(env);

        // 定义源表:使用处理时间(PROCTIME())
        tEnv.executeSql(
            "CREATE TABLE source_table (\n" +
            "  username STRING,\n" +
            "  price INT,\n" +
            "  proc_time AS PROCTIME()                        -- 处理时间列,自动生成\n" +
            ") WITH (\n" +
            "  'connector' = 'kafka',\n" +
            "  'topic' = 'topic2',\n" +
            "  'properties.bootstrap.servers' = 'hadoop102:9092',\n" +
            "  'properties.group.id' = 'g2',\n" +
            "  'scan.startup.mode' = 'latest-offset',\n" +
            "  'format' = 'json'\n" +
            ")"
        );

        // 滑动窗口:窗口大小 60 秒,滑动步长 10 秒
        tEnv.executeSql(
            "SELECT\n" +
            "  window_start,\n" +
            "  window_end,\n" +
            "  username,\n" +
            "  COUNT(1) AS zongNum,\n" +
            "  SUM(price) AS totalMoney\n" +
            "FROM TABLE(\n" +
            "  HOP(TABLE source_table, DESCRIPTOR(proc_time), INTERVAL '10' SECOND, INTERVAL '60' SECOND)\n" +
            ")\n" +
            "GROUP BY window_start, window_end, username"
        ).print();
    }
}
  • 处理时间:通过 AS PROCTIME() 定义虚拟列,无需真实事件时间。
  • 滑动窗口语法:HOP(TABLE 表名, DESCRIPTOR(时间字段), 滑动步长, 窗口大小)。
  • 输出结果中,同一事件会出现在多个窗口(窗口重叠)

(三)两种窗口的对比

对比维度 滚动窗口 (TUMBLE) 滑动窗口 (HOP)
窗口重叠性 完全无重叠(边界对齐,左闭右开) 高度重叠(数据重复计算 N 次)
触发频率 (每隔 1 个窗口长度触发 1 次) (每隔 1 个滑动步长触发 1 次)
状态大小 1 个窗口 / Key(状态小) N 个窗口 / Key (N=长度/步长,状态膨胀严重
资源消耗 ⭐ 低(推荐) ⭐⭐⭐⭐⭐ 极高(易导致反压,谨慎使用)
SQL 语法 TUMBLE(TABLE t, DESCRIPTOR(ts), INTERVAL '60' SECOND) HOP(TABLE t, DESCRIPTOR(ts), INTERVAL '10' SECOND, INTERVAL '60' SECOND)
参数顺序 (表, 时间列, 窗口大小) (表, 时间列, 滑动步长, 窗口大小) ------ 步长在前,大小在后!
适用场景 每分钟 PV/UV、每小时 GMV、每日活跃用户 每隔 10 秒统计最近 1 分钟平均响应时间、实时移动平均线


总结

  • 本文围绕 Flink Table API 与 SQL,从基础概念到实战应用进行了系统性的梳理。核心要点总结如下:

  • 统一批流处理:Table API 为批处理和流处理提供了完全一致的上层接口,开发者无需切换思维模式即可处理两种场景。通过 StreamTableEnvironment 建立表环境,是开启所有 Table 程序的第一步。

  • 核心开发四步法:一个完整的 Table 程序遵循 "创建表环境 → 定义 Source 表(读取数据)→ 执行查询转换(使用 SQL 或 Table API)→ 定义 Sink 表并输出结果" 的标准流程。其中,DataStream 与 Table 之间的双向转换(fromDataStream/createTemporaryView 与 toDataStream/toChangelogStream)是衔接流处理底层能力与高层 SQL 语法的关键桥梁,尤其对于包含更新操作(如分组聚合)的表,必须使用 toChangelogStream 捕获变更日志。

  • 外部系统集成:通过 Connector(如 Kafka、JDBC),Table API 可以轻松读写外部数据源。文中提供的 Kafka 和 MySQL 案例清晰地展示了如何通过 DDL 语句定义 Source 和 Sink,实现了实时数据过滤与落地的典型 ETL 场景。

  • 窗口聚合是流处理的核心:针对无限流数据,Flink 提供了基于表值函数(TVF)的窗口语法。其中:

  • 滚动窗口(TUMBLE):窗口固定大小、无重叠,适用于标准时间粒度(如每分钟、每小时)的统计,资源消耗低,应优先考虑。

  • 滑动窗口(HOP):窗口允许重叠,能输出更平滑的统计曲线(如每10秒统计过去1分钟数据),但需注意其状态膨胀和计算开销显著高于滚动窗口,使用时应谨慎评估性能。

  • 总之,Flink Table API 与 SQL 极大地降低了实时计算的门槛,使得数据开发人员能够以更熟悉的关系模型来处理流数据。掌握其基本开发范式、转换规则及窗口语义,是构建高效、稳定的实时数据应用的基础。 希望本文能为您在实际项目中使用 Flink 进行数据分析提供有力的参考。