文章目录
- 前言
- [一、Table API 是什么](#一、Table API 是什么)
- 二、开发步骤
- 三、connect连接
-
- [(一)案例一:Kafka Connector](#(一)案例一:Kafka Connector)
- [(二)JDBC Connector案例2](#(二)JDBC Connector案例2)
- [四、Table API 的窗口](#四、Table API 的窗口)
-
- [(一)滚动窗口 Tumble](#(一)滚动窗口 Tumble)
- [(二)滑动窗口 HOP](#(二)滑动窗口 HOP)
- (三)两种窗口的对比
- 总结
前言
-
随着大数据实时处理需求的日益增长,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 进行数据分析提供有力的参考。