4. Flink 核心概念:DataStream、Source、Transformation、Sink、JobManager、TaskManager
好,咱们进入第四章。这一章我打算把 Flink 的六个核心概念掰开揉碎了讲清楚。你可能会觉得这些名词在官方文档里都见过,但说实话,我当年刚接触 Flink 时,光是搞明白 JobManager 和 TaskManager 到底谁管谁,就花了不少功夫。
这六个概念,说白了就是 Flink 应用的骨架和血肉。你写一个 Flink 作业,本质上就是在跟它们打交道。咱们一个一个来。
4.1 DataStream:流动的数据本身
DataStream 是 Flink 里最核心的数据抽象。你可以把它想象成一条永不停歇的数据河流。每条数据就是一个事件,它们按顺序流过来。
核心要点:DataStream 代表一个无限的数据流。它不关心数据总量,只关心当前这条数据怎么处理。
我在项目中遇到过一个问题:刚上手时总把 DataStream 当成一个集合(Collection)来理解,想着“等数据到齐了再处理”。这其实是个误区。Flink 是流式处理,数据永远到不齐。你只能一条一条地处理,或者按时间窗口攒一批处理。
DataStream 本身不存数据,它只是一个逻辑视图。真正干活的是它背后的算子(Operator)。
4.2 Source:数据从哪来
Source 是 Flink 作业的入口。没有 Source,你的作业就是无源之水。
常见的 Source 有几种:
- 文件 Source:读取本地或 HDFS 上的文件,支持 Text、CSV、Parquet 等格式。
- Socket Source:从网络 Socket 读取数据,测试时常用。
- Kafka Source:从 Kafka Topic 消费数据,这是咱们课程的重点。
- 自定义 Source:实现 SourceFunction 接口,从任何数据源拉数据。
写一个最简单的 Kafka Source 示例:
DataStream<String> stream = env
.addSource(new FlinkKafkaConsumer<>(
"my-topic",
new SimpleStringSchema(),
properties
));
我的习惯:生产环境中,我一般会给 Source 设置一个 setStartFromLatest() 或 setStartFromEarliest(),避免每次重启都从头消费。具体用哪个,取决于业务能不能容忍丢数据。
4.3 Transformation:数据怎么变
Transformation 是 Flink 作业的“加工车间”。Source 把原材料拉进来,经过一系列 Transformation,变成你想要的输出。
常见的 Transformation 包括:
| 算子 | 作用 | 一对一还是多对多 |
|---|---|---|
map |
一条进,一条出,类型可以变 | 一对一 |
flatMap |
一条进,零到多条出 | 一对一 |
filter |
保留符合条件的记录 | 一对一 |
keyBy |
按 key 分组,后续算子可以并行处理 | 多对多(shuffle) |
window |
按时间或数量攒批 | 多对多 |
举个例子,从 Kafka 读到的 JSON 字符串,先解析成 Java 对象,再过滤掉无效数据,最后按用户 ID 分组:
DataStream<UserEvent> parsed = stream
.map(json -> JsonUtil.parse(json, UserEvent.class));
DataStream<UserEvent> valid = parsed
.filter(event -> event.getUserId() != null);
DataStream<UserEvent> keyed = valid
.keyBy(UserEvent::getUserId);
这里要注意,keyBy 之后的数据流类型变成了 KeyedStream,它比普通的 DataStream 多了窗口和状态操作的能力。
我曾经踩过的坑:在 keyBy 之前用了 map 做状态操作,结果状态一直不生效。后来才意识到,状态操作必须在 keyBy 之后才能用。说白了,Flink 的状态是按 key 分片的,没有 key 就没法分片。
4.4 Sink:数据去哪了
Sink 是 Flink 作业的出口。数据经过 Transformation 加工后,最终要写到某个地方去。
常见的 Sink 包括:
- Kafka Sink:写回 Kafka 的另一个 Topic。
- JDBC Sink:写入 MySQL、PostgreSQL 等关系型数据库。
- File Sink:写入 HDFS 或本地文件系统。
- Elasticsearch Sink:写入 ES 用于搜索和分析。
写一个 Kafka Sink 的示例:
stream
.addSink(new FlinkKafkaProducer<>(
"output-topic",
new SimpleStringSchema(),
properties
));
嗯,这里要提醒一下:Sink 的并行度可以单独设置。如果你的下游系统扛不住高并发,可以调低 Sink 的并行度,甚至设为 1。
4.5 JobManager:作业的大脑
JobManager 是 Flink 集群的“大脑”。它负责三件事:
- 接收作业:客户端提交的作业,先到 JobManager 手里。
- 生成执行计划:JobManager 把 DataStream 的 DAG(有向无环图)转换成物理执行计划。
- 协调调度:把任务分发给 TaskManager,并监控它们的运行状态。
你想想看,JobManager 就像是一个项目经理。它不亲自写代码,但它知道谁该干什么,什么时候干,干不完怎么办。
如果 JobManager 挂了,整个集群就瘫痪了。所以生产环境一般会配置 High Availability(HA),用 ZooKeeper 做 Leader 选举,保证 JobManager 挂了之后能自动切换。
4.6 TaskManager:作业的双手
TaskManager 是真正干活的“工人”。每个 TaskManager 是一个 JVM 进程,里面可以运行多个 Task Slot(任务槽)。
Task Slot 是 Flink 的最小资源单位。一个 Task Slot 可以运行一个线程。你可以把 Task Slot 理解为工位,每个工位只能坐一个人。
举个例子:如果你的作业有 4 个并行度,每个 TaskManager 有 2 个 Slot,那就需要 2 个 TaskManager 来跑。
关键区别:JobManager 管“做什么”,TaskManager 管“怎么做”。JobManager 发号施令,TaskManager 埋头干活。
4.7 它们怎么配合?一张图看懂
下面这张 SVG 图展示了从 Source 到 Sink 的完整数据流,以及 JobManager 和 TaskManager 的分工。
4.8 总结一下
这六个概念,我建议你按这个顺序理解:
- Source 把数据拉进来,变成 DataStream。
- Transformation 对 DataStream 做各种加工。
- Sink 把加工后的数据写出去。
- 这一切的调度和执行,由 JobManager 和 TaskManager 协作完成。
说白了,Flink 作业就是一个“Source → Transformation → Sink”的管道。JobManager 负责管道的设计图纸,TaskManager 负责按图纸施工。
我个人习惯在写作业前,先在纸上画出这个管道图。哪一步用 map,哪一步用 keyBy,Sink 写到哪里,心里有数了再动手。这样写出来的代码,结构清晰,不容易出 bug。