3. Flink 基础入门:架构、运行模式与第一个程序
好,咱们正式开始接触 Flink。这一章我会带你从架构概览、运行模式,一直走到本地开发环境搭建,最后跑通第一个流处理程序。嗯,内容不少,但每一步我都会穿插一些实际项目里的经验,帮你少走弯路。
3.1 Flink 架构:核心组件与角色
先看一张整体架构图,把 Flink 的“骨架”搭起来。我个人习惯把 Flink 集群分成三个角色:Client、JobManager 和 TaskManager。
一句话总结:Client 提交作业,JobManager 负责调度,TaskManager 真正干活。
这里有个关键概念——Slot。每个 TaskManager 可以配置多个 Slot,Slot 是资源调度的最小单位。我记得刚带团队时,有人把 Slot 理解成“线程池里的线程”,其实不完全对。Slot 更像是“资源槽位”,它固定了内存和 CPU 的份额,但一个 Slot 里可以运行多个 subtask(子任务)。
| 组件 | 职责 | 我见过的坑 |
|---|---|---|
| Client | 解析代码、生成 JobGraph、提交给 JobManager | Client 挂了不影响已提交的作业 |
| JobManager | 调度、Checkpoint、故障恢复 | 单点故障?用 HA 模式解决 |
| TaskManager | 执行 Task,管理 Slot | Slot 数别超过 CPU 核数太多 |
3.2 运行模式:Session、Per-Job、Application
Flink 支持三种运行模式。你想想看,如果每次提交作业都启动一个集群,是不是太浪费了?反过来,如果所有作业共享一个集群,隔离性又差。所以 Flink 给了你选择。
- Session 模式:先启动集群,再提交作业。适合短作业、交互式调试。缺点:作业之间争抢资源,一个作业 OOM 可能拖垮整个集群。
- Per-Job 模式:每个作业独享一个集群。资源隔离好,但启动慢。适合生产环境的长作业。
- Application 模式:从 Application Jar 启动,main 方法在集群上执行。解决了 Per-Job 模式下 Client 需要一直在线的问题。
我的建议:开发测试用 Session 模式,生产环境用 Per-Job 或 Application 模式。我曾经在 Session 模式下跑过 20 多个作业,结果一个作业的背压把整个集群搞挂了——从那以后,生产环境我再也没用过 Session。
3.3 开发环境搭建:从 JDK 到 IDE
环境搭建其实不难,但版本匹配是个容易踩坑的地方。我直接给你一个经过验证的组合。
- JDK 8 或 11:Flink 1.13+ 对 JDK 11 支持很好,但我个人习惯用 JDK 8,兼容性最稳。
- Maven 3.6+:用于管理依赖和打包。
- IDE:IntelliJ IDEA 社区版就够用,记得装 Flink 插件(其实不装也行,但装了能帮你检查代码)。
- Flink 本地包:从官网下载二进制包,解压后直接运行
./bin/start-cluster.sh就能启动单机版。
注意:Windows 用户请用 WSL2 或 Git Bash,别用 cmd 跑 Flink 脚本。我见过有人用 cmd 跑 start-cluster,结果日志乱码、进程杀不掉——嗯,别问我怎么知道的。
3.4 第一个 Flink 流处理程序:WordCount
好,到了动手环节。我们写一个经典的流式 WordCount,从 socket 读取数据,实时统计单词出现次数。
先创建一个 Maven 项目,在 pom.xml 里引入 Flink 依赖:
<properties>
<flink.version>1.14.4</flink.version>
<scala.binary.version>2.12</scala.binary.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
然后写主程序:
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;
public class SocketWordCount {
public static void main(String[] args) throws Exception {
// 1. 创建流执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 2. 从 socket 读取数据(本地 9999 端口)
DataStream<String> text = env.socketTextStream("localhost", 9999);
// 3. 转换:切分单词,计数 1
DataStream<Tuple2<String, Integer>> counts = text
.flatMap(new Tokenizer())
.keyBy(value -> value.f0)
.sum(1);
// 4. 打印结果
counts.print();
// 5. 执行
env.execute("Socket WordCount");
}
public static class Tokenizer implements FlatMapFunction<String, Tuple2<String, Integer>> {
@Override
public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {
String[] words = value.toLowerCase().split("\\W+");
for (String word : words) {
if (word.length() > 0) {
out.collect(new Tuple2<>(word, 1));
}
}
}
}
}
代码逻辑很清晰:
env.socketTextStream创建一个 socket 数据源flatMap把每行文本切分成单词keyBy按单词分组,sum(1)累加次数print输出到控制台
运行步骤:
- 启动 Flink 集群:
./bin/start-cluster.sh - 启动 netcat:
nc -lk 9999 - 在 IDE 里运行 main 方法,或者在命令行用
flink run -c SocketWordCount your-jar.jar - 在 netcat 窗口输入单词,看控制台输出
你输入 hello world hello,输出应该是:
(hello,1)
(world,1)
(hello,2)
看到这个结果,说明你的 Flink 环境已经跑通了。嗯,第一个程序就这么简单。别小看它,这个模式——source → transformation → sink——是所有 Flink 作业的骨架。
3.5 避坑指南:我踩过的三个坑
- 坑一:依赖 scope 忘记设 provided。如果你在 IDE 里直接跑,记得把 flink 依赖的 scope 改成
provided,否则会和集群自带的 jar 冲突。我曾经因为这个浪费了半天。 - 坑二:socket 端口被占用。Flink 默认的 JobManager RPC 端口是 6123,TaskManager 数据端口是 6124。如果你本地有其他服务占用了这些端口,启动会失败。用
lsof -i:6123检查一下。 - 坑三:并行度设置不合理。本地调试时并行度默认是 CPU 核数,但 socket 源只能单线程读取。如果你设了并行度 > 1,反而会出问题。建议调试时用
env.setParallelism(1)。
一个小技巧:在 flink-conf.yaml 里把 rest.port: 8081 打开,启动集群后访问 http://localhost:8081 就能看到 Web UI。你可以在这里查看作业状态、背压、Checkpoint 等信息——非常实用。
好了,这一章的内容就到这里。你学会了 Flink 的架构、三种运行模式、环境搭建,还亲手跑了一个流处理程序。接下来,你可以试着把 socket 源换成 Kafka,或者把输出改成文件——这些改动会让你对 Flink 的理解更深一层。