3. Flink编程模型:DataStream API与DataSet API的演进
Flink 从诞生至今,其编程模型经历了从“两套 API 并行”到“流批一体”的重大演进。理解这一演进过程,是掌握 Flink 程序骨架(Source、Transformation、Sink)的前提。本章将梳理 DataStream API 与 DataSet API 的差异、合并历程,并给出 Flink 程序的标准结构。
3.1 编程模型演进概览
Flink 最初为批处理和流处理分别设计了不同的 API:
- DataSet API:面向有界数据集(批处理),基于“数据集”抽象,操作如
map、reduce、groupBy,执行时采用批调度策略。 - DataStream API:面向无界数据流(流处理),基于“数据流”抽象,操作如
keyBy、window、process,执行时采用流调度策略。
从 Flink 1.12 开始,官方正式推进“流批一体”,将 DataSet API 逐步合并到 DataStream API 中。当前推荐统一使用 DataStream API 编写所有作业,通过设置执行模式(STREAMING / BATCH)来切换流/批语义。
| 维度 | DataSet API(旧) | DataStream API(推荐) |
|---|---|---|
| 数据模型 | 有界数据集 | 无界/有界数据流 |
| 执行模式 | 批模式 | 流模式 / 批模式 |
| 窗口支持 | 有限(基于分组) | 丰富(时间窗口、计数窗口、会话窗口等) |
| 状态管理 | 无原生状态 | Keyed State / Operator State |
| 事件时间 | 不支持 | 原生支持(Watermark、Timestamp) |
| 当前状态 | 已标记为废弃(Flink 1.18+) | 主推 API,持续演进 |
学习建议: 新项目请直接使用 DataStream API。即使处理静态文件,也通过
env.setRuntimeMode(RuntimeExecutionMode.BATCH) 启用批模式,无需再引入 DataSet API。
3.2 Flink程序基本骨架:Source → Transformation → Sink
无论流还是批,Flink 程序都遵循统一的“三段式”骨架:
- Source(数据源):从外部系统读取数据,如 Kafka、文件、Socket、自定义 Source。
- Transformation(转换算子):对数据进行处理,如过滤、映射、聚合、开窗、连接等。
- Sink(数据输出):将结果写入外部系统,如 Kafka、Elasticsearch、文件、数据库、自定义 Sink。
核心公式: Flink 作业 = Source + Transformation 链 + Sink。所有算子通过 DataStream 串联,形成 DAG(有向无环图)。
3.3 程序骨架的SVG流程图
3.4 完整代码示例(流批一体)
以下示例展示一个完整的 Flink 程序,从 Socket 读取文本,进行词频统计,输出到控制台:
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.util.Collector;
public class WordCountStreaming {
public static void main(String[] args) throws Exception {
// 1. 创建执行环境(流批一体)
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 可选:设置为批模式
// env.setRuntimeMode(RuntimeExecutionMode.BATCH);
// 2. Source:从 Socket 读取文本流
DataStream<String> textStream = env.socketTextStream("localhost", 9999);
// 3. Transformation:分词、映射为 (word, 1)、分组求和
DataStream<Tuple2<String, Integer>> wordCounts = textStream
.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
@Override
public void flatMap(String line, Collector<Tuple2<String, Integer>> out) {
for (String word : line.split("\\s+")) {
out.collect(new Tuple2<>(word, 1));
}
}
})
.keyBy(value -> value.f0)
.sum(1);
// 4. Sink:打印到控制台
wordCounts.print();
// 5. 执行作业
env.execute("Flink WordCount Streaming");
}
}
注意: 上述代码中
keyBy 使用 Lambda 表达式(value -> value.f0),要求 Java 8+。如果使用 DataSet API 的 groupBy,在 Flink 1.18+ 中会触发废弃警告,请迁移至 DataStream API。
3.5 常用Source与Sink对照表
| 类别 | Source 示例 | Sink 示例 |
|---|---|---|
| 消息队列 | KafkaSource、RabbitMQ | KafkaSink、PulsarSink |
| 文件系统 | FileSource(TextLineFormat) | FileSink(Bulk / Streaming) |
| 数据库 | JDBCSource(Table API) | JDBCSink、ElasticsearchSink |
| 网络 | SocketTextStream | SocketSink(较少用) |
| 自定义 | 实现 SourceFunction / ParallelSourceFunction | 实现 SinkFunction |
3.6 执行模式对比
Flink 支持三种执行模式,通过 RuntimeExecutionMode 指定:
| 模式 | 适用场景 | 特点 |
|---|---|---|
STREAMING |
无界流(实时) | 默认模式,逐条处理,支持事件时间、Watermark、Checkpoint |
BATCH |
有界数据(批处理) | 优化吞吐,按批次调度,不支持事件时间/Watermark(自动退化) |
AUTOMATIC |
自动推断 | 根据 Source 是否有界自动选择,有界则 BATCH,无界则 STREAMING |
最佳实践: 在开发阶段使用
AUTOMATIC 模式,让 Flink 自动判断;生产环境明确指定 STREAMING 或 BATCH,避免因 Source 变更导致执行模式意外切换。
3.7 演进总结:从两套API到统一DataStream
3.8 本章要点
- API选择:始终使用 DataStream API,通过
RuntimeExecutionMode切换流/批。 - 程序骨架:Source → Transformation → Sink,三者缺一不可。
- 延迟执行:所有算子构建 DAG,调用
env.execute()后才真正提交作业。 - Source/Sink 生态:Flink 提供丰富的连接器(Kafka、文件、JDBC、ES 等),也支持自定义。
- 演进方向:流批一体是 Flink 的核心优势,后续章节将深入 DataStream API 的各类算子与状态管理。