为什么有 Table API/SQL
DataStream API 提供细粒度控制,但代码量较大。Table API/SQL 是声明式接口:
// DataStream API:20 行代码
stream.keyBy(Event::getSymbol)
.window(TumblingEventTimeWindows.of(Duration.ofMinutes(1)))
.aggregate(new CountAggregate());
// SQL:1 行
SELECT symbol, COUNT(*) FROM events
GROUP BY symbol, TUMBLE(event_time, INTERVAL '1' MINUTE)
Table API/SQL 适合:
- ETL 管道(数据转换)
- 分析查询(聚合、Join)
- 不熟悉 Java 的数据分析师
TableEnvironment
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
// 注册 Kafka 表
String createTable = """
CREATE TABLE events (
eventId STRING,
symbol STRING,
amount DOUBLE,
-- v3 的 eventTime 是毫秒时间戳(JSON 里是数字),先按 BIGINT 读,
-- 再算出一个 TIMESTAMP_LTZ 列给 Watermark 用
eventTime BIGINT,
event_time AS TO_TIMESTAMP_LTZ(eventTime, 3),
WATERMARK FOR event_time AS event_time - INTERVAL '10' SECOND
) WITH (
'connector' = 'kafka',
'topic' = 'eventd-cleaned',
'properties.bootstrap.servers' = 'kafka:9092',
'properties.group.id' = 'eventd-sql', -- Source 表必填
'format' = 'json'
)
""";
tableEnv.executeSql(createTable);
SQL 查询
// 滚动窗口聚合(1.20 推荐的窗口 TVF 写法)
Table result = tableEnv.sqlQuery("""
SELECT
window_start,
window_end,
symbol,
COUNT(*) as order_count,
SUM(amount) as total_amount
FROM TABLE(
TUMBLE(TABLE events, DESCRIPTOR(event_time), INTERVAL '1' MINUTE))
GROUP BY window_start, window_end, symbol
""");
DataStream ↔ Table 转换
// DataStream → Table
DataStream<Event> stream = ...;
Table table = tableEnv.fromDataStream(
stream,
$("eventId"), $("symbol"), $("amount"), $("eventTime").rowtime());
// Table → DataStream:只接受 add-only 结果(窗口聚合属于这种)
DataStream<Row> resultStream = tableEnv.toDataStream(result);
// Table → 通用变更流:普通 GROUP BY 这类会更新结果、可能撤回旧行的查询必须用这个
DataStream<Row> changelogStream = tableEnv.toChangelogStream(result);
toDataStream 在遇到非 add-only 的结果时会抛出 TableException(提示只支持 insert-only 变更);toRetractStream 在 1.20 已标记废弃,新代码统一用 toChangelogStream。
fromDataStream 里的 $("eventTime").rowtime() 要求这个 DataStream 已经分配过时间戳和 Watermark;只加字段名、没有分配 Watermark 时,运行期会报「The stream must be timestamped and watermarked」。
混合使用
DataStream 和 Table 可以混合:
// 1. DataStream 读取 Kafka
DataStream<Event> stream = env.fromSource(kafkaSource, watermarkStrategy, "events");
// 2. 转为 Table 用 SQL 聚合
Table table = tableEnv.fromDataStream(stream, ...);
tableEnv.createTemporaryView("events", table);
Table aggregated = tableEnv.sqlQuery("SELECT ... FROM events GROUP BY ...");
// 3. 转回 DataStream 做 CEP
DataStream<Row> aggregatedStream = tableEnv.toDataStream(aggregated);
Pattern<Row, ?> pattern = Pattern.<Row>begin("start").where(...);
CEP.pattern(aggregatedStream, pattern).select(...);
本卷的立场
本卷主要使用 DataStream API,原因:
- 状态控制:DataStream 可以精确控制
ValueState/ListState - CEP 模式:DataStream API 可直接使用 CEP 库;SQL 侧虽然有
MATCH_RECOGNIZE,但复杂模式、状态控制和与自定义算子的组合不如 Java CEP 灵活 - 自定义逻辑:eventd v3 的窗口逻辑和告警规则需要 Java 代码
- 与 Kafka Streams 衔接:DataStream 和 Kafka Streams 概念更接近
Table API/SQL 用于:
- 快速原型验证
- 简单的 ETL(数据格式转换)
- 与分析师协作的查询
小结
| API | 层级 | 适用 |
|---|---|---|
| SQL | 最高 | ETL、分析、快速查询 |
| Table API | 中高 | 声明式流处理 |
| DataStream API | 低 | 自定义状态、CEP、精细控制 |
本卷聚焦 DataStream API,Table API/SQL 仅作概述。需要深入了解可参考 Flink 官方文档。
第 6 章结束。下一节 7.1 连接器:Kafka Source/Sink 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。