4. Flink基础入门:核心概念、DataStream API、环境搭建与第一个实时任务
好,咱们进入第四章。这一章是地基中的地基。你想想看,一个实时推荐平台,数据像流水一样哗哗地进来,你得有个东西能接得住、算得快、还不丢数据。Flink 就是干这个的。
我记得我刚接触 Flink 那会儿,第一反应是:这玩意儿跟 Spark Streaming 有啥区别?后来踩了几个坑才明白,Flink 的「真·实时」和「精确一次」语义,在推荐场景下简直是天作之合。今天我就把当年摸索出来的东西,掰开了揉碎了讲给你。
4.1 Flink 核心概念:你必须要搞懂的三个东西
Flink 的概念其实不多,但每个都很关键。我个人习惯把核心抽象归纳为三层:流、算子、状态。
| 概念 | 一句话解释 | 我踩过的坑 |
|---|---|---|
| DataStream | 无限流动的数据集合,有界/无界 | 一开始总把无界流当批处理写,结果 watermark 没设对,窗口永远不触发 |
| 算子(Operator) | map、filter、keyBy、window 等转换逻辑 | keyBy 之后没做 rebalance,数据倾斜导致 OOM |
| 状态(State) | 算子内部保存的中间数据,支持容错 | 忘了开 checkpoint,任务挂了全量重算,被老板骂了一顿 |
说白了,Flink 就是一个「数据管道工厂」。你把数据源接在入口,把处理逻辑串成管道,把结果送到出口。中间所有「记住的东西」就是状态。
核心要点:Flink 的「有状态计算」是它跟普通消息队列处理最大的区别。没有状态,你只能做无脑过滤;有了状态,你才能做累计、去重、关联、窗口聚合——推荐系统里 80% 的逻辑都依赖状态。
4.2 DataStream API:你写 Flink 任务的主要战场
DataStream API 是 Flink 对开发者暴露的核心编程接口。我建议你把它想象成 Java 8 的 Stream API 的「实时加强版」。
4.2.1 核心算子速览
- Source:数据从哪来?Kafka、Socket、文件、自定义 Source。
- Transformation:数据怎么变?map、flatMap、filter、keyBy、window、process。
- Sink:数据往哪去?Kafka、MySQL、Elasticsearch、Redis、自定义 Sink。
嗯,这里要注意:keyBy 不是分组,是分区。它决定了相同 key 的数据进入同一个 subtask,这是后续 window 和 state 能正确工作的前提。
我的小技巧:写 Flink 任务时,先把 Source 和 Sink 写好,中间用 .print() 代替 Sink 调试。等逻辑跑通了,再换成真正的 Sink。这样能省一半的排错时间。
4.2.2 一个完整的 DataStream 任务骨架
// 1. 创建执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 2. 设置 checkpoint(我建议生产环境必开)
env.enableCheckpointing(5000); // 每 5 秒一次
// 3. 定义数据源
DataStream<String> sourceStream = env.socketTextStream("localhost", 9999);
// 4. 转换逻辑
DataStream<WordCount> wordCounts = sourceStream
.flatMap(new Tokenizer())
.keyBy(word -> word.f0)
.window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
.sum(1);
// 5. 输出结果
wordCounts.print();
// 6. 启动任务
env.execute("My First Flink Job");
这段代码我当年写了不下 50 遍。你注意看,env.execute() 是必须的,不调用它 Flink 不会真正启动。我曾经调试了半天发现没输出,结果就是忘了这行。
4.3 环境搭建:本地跑起来再说
我建议你从本地模式开始。别一上来就搞集群,先让代码在 IDE 里跑通,心里有底了再上分布式。
4.3.1 本地模式(推荐新手)
- 下载 Flink 二进制包(我习惯用 1.13+ 版本)
- 解压后运行
./bin/start-cluster.sh - 访问
http://localhost:8081看 Web UI - 在 IDE 里写代码,直接 Run 就行
避坑指南:我曾经在本地跑任务时,发现 Web UI 上一直显示「No jobs running」。查了半天,原来是 IDE 里默认用了 setParallelism(1),而 Web UI 只显示分布式执行的任务。解决办法:在 env.execute() 之前不要手动设并行度,或者设成大于 1。
4.3.2 集群模式(生产环境)
生产环境我推荐用 Standalone 或 YARN 模式。核心步骤就三步:
- 配置
flink-conf.yaml(jobmanager.rpc.address、taskmanager.numberOfTaskSlots 等) - 分发 Flink 包到所有节点
- 启动集群:
./bin/start-cluster.sh
你想想看,本地和集群的区别其实就是「谁来分配资源」。本地是 JVM 自己管,集群是 YARN/K8s 管。代码本身几乎不用改。
4.4 第一个 Flink 实时任务:从 Socket 读取并实时统计
好,咱们动手写一个。这个任务虽然简单,但包含了 Flink 实时任务的所有核心要素。
4.4.1 任务目标
从 Socket 端口读取文本流,每隔 10 秒统计一次单词出现次数,输出到控制台。
4.4.2 完整代码
public class SocketWordCount {
public static void main(String[] args) throws Exception {
// 环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(2);
// 数据源:监听 9999 端口
DataStream<String> text = env.socketTextStream("localhost", 9999);
// 转换:分词 -> 计数
DataStream<Tuple2<String, Integer>> counts = text
.flatMap((String line, Collector<Tuple2<String, Integer>> out) -> {
for (String word : line.split("\\s+")) {
out.collect(new Tuple2<>(word, 1));
}
})
.returns(Types.TUPLE(Types.STRING, Types.INT))
.keyBy(value -> value.f0)
.window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
.sum(1);
// 输出
counts.print();
// 执行
env.execute("Socket Word Count");
}
}
4.4.3 运行步骤
- 启动 Flink 本地集群(或 IDE 直接运行)
- 在终端执行
nc -lk 9999启动 Socket 服务 - 在 IDE 里运行上面的代码
- 在终端输入单词,比如
hello flink hello world - 观察控制台输出:每 10 秒打印一次统计结果
调试技巧:如果没输出,先检查 nc 是否正常,再检查 Flink Web UI 上任务是否在运行。我遇到过最蠢的坑是防火墙没关 9999 端口。
4.5 本章知识体系总览
下面这张图是我自己总结的 Flink 入门知识结构,你照着这个脉络学,不会迷路。
这张图把本章的四个模块串起来了。你从左上角的核心概念开始,顺着箭头走到右下角的任务执行,每一步都是下一环的基础。
总结一下:Flink 入门其实就三件事——搞懂流和状态、学会 DataStream API、把环境搭起来跑通第一个任务。别贪多,先把这三点吃透,后面的窗口、时间语义、状态后端、容错机制都是锦上添花。