4. Flink基础入门:Flink架构、运行环境搭建、DataStream API初探、WordCount实战
好,我们正式开始接触实时计算的核心引擎——Flink。这一章是地基,我会尽量讲得实在一些。你想想看,如果连Flink怎么跑起来的都不清楚,后面做ODS到DWD的清洗逻辑肯定会踩坑。我个人习惯是,学一个新东西先看它的骨架,再动手搭环境,最后跑个Demo验证手感。咱们就按这个节奏来。
4.1 Flink架构:它到底是怎么工作的?
Flink本质上是一个「有状态的流处理引擎」。说白了,它把一切数据都当成流来处理——即使是批数据,也被它当作「有界流」。这个思想很关键,后面你会体会到。
它的架构分为两层:运行时层和API层。
- JobManager:集群的「大脑」。负责调度任务、协调Checkpoint、故障恢复。我遇到过一个问题:JobManager挂掉后任务全崩,后来才知道高可用必须配ZooKeeper。
- TaskManager:真正干活的「工人」。每个TaskManager有多个Slot,Slot就是计算资源的最小单位。一个Slot可以跑一个子任务。
- Client:提交任务的入口。它把作业编译成JobGraph,发给JobManager,然后就可以退出了(除非你跑的是交互模式)。
核心概念:并行度(Parallelism)决定了一个算子有多少个并行实例。我刚开始调优时,总以为并行度越大越好,结果资源浪费严重。记住:并行度 = min(数据分区数, 可用Slot数)。
下面这张图展示了Flink的运行时架构和数据流转关系,我建议你多看两眼,后面调试任务时你会反复用到这些概念。
4.2 运行环境搭建:别让环境卡住你
搭建Flink环境其实不复杂,但有几个细节容易翻车。我建议你直接用本地模式先跑起来,等熟悉了再上集群。
4.2.1 前置条件
- JDK 8 或 11(我推荐JDK 11,Flink 1.14+对JDK 11支持更好)
- Maven 3.6+(如果你用Java开发)
- IDE(IntelliJ IDEA最佳,记得装Scala插件)
4.2.2 下载与启动
# 下载Flink(以1.17为例)
wget https://dlcdn.apache.org/flink/flink-1.17.0/flink-1.17.0-bin-scala_2.12.tgz
tar -xzf flink-1.17.0-bin-scala_2.12.tgz
cd flink-1.17.0
# 启动本地集群
./bin/start-cluster.sh
# 检查是否启动成功
jps # 应该看到 StandaloneSessionClusterEntrypoint 和 TaskManagerRunner
启动后,浏览器打开 http://localhost:8081,你会看到Flink的Web UI。嗯,这里要注意:如果8081端口被占用,可以在 conf/flink-conf.yaml 里改 rest.port。
小技巧:我习惯在 flink-conf.yaml 里把 taskmanager.numberOfTaskSlots 设成4,这样本地调试时可以跑多个并行子任务,方便测试并行度逻辑。
4.2.3 项目依赖
如果你用Maven,pom.xml里加这些依赖:
<properties>
<flink.version>1.17.0</flink.version>
<scala.binary.version>2.12</scala.binary.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients</artifactId>
<version>${flink.version}</version>
</dependency>
</dependencies>
避坑:我曾经因为 flink-streaming-java 和 flink-clients 版本不一致,导致运行时ClassNotFoundException。记住:所有Flink依赖的版本号必须完全一致。
4.3 DataStream API初探:核心概念速览
DataStream API是Flink处理无界流数据的核心API。说白了,你写的每个算子都是在操作一个「无限的数据流」。我刚开始接触时,总觉得它跟Spark Streaming很像,但用久了发现Flink的「事件驱动」模型更灵活。
几个核心概念你必须记住:
| 概念 | 说明 | 我的经验 |
|---|---|---|
| Source | 数据输入源(Kafka、Socket、文件等) | 生产环境90%用Kafka Source |
| Transformation | 数据转换算子(map、filter、keyBy等) | keyBy后一定要做状态清理,否则内存会爆 |
| Sink | 数据输出目标(Kafka、MySQL、HDFS等) | 记得开启Exactly-Once语义 |
| KeyedStream | 按key分区的流,后续可以做状态操作 | keyBy的key不能为null,否则运行时抛异常 |
| Window | 将无界流切分成有限块做聚合 | 事件时间窗口要配合Watermark使用 |
一个典型的DataStream作业流程是这样的:
Source(读数据) → Transformation(清洗/转换) → KeyBy(分组) → Window(开窗) → Sink(输出)
4.4 WordCount实战:第一个Flink程序
好了,理论说够了,咱们动手写个WordCount。这是实时计算界的「Hello World」,但我会加点料——让它从Socket读数据,实时统计单词出现次数。
4.4.1 完整代码
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();
env.setParallelism(2); // 设置并行度,我习惯先设2,调试方便
// 2. 从Socket读取数据(本地用nc命令模拟)
DataStream<String> textStream = env.socketTextStream("localhost", 9999);
// 3. 数据转换:切分单词,计数1
DataStream<Tuple2<String, Integer>> wordCounts = textStream
.flatMap(new Tokenizer()) // 切分单词
.keyBy(value -> value.f0) // 按单词分组
.sum(1); // 累加计数
// 4. 输出结果(打印到控制台)
wordCounts.print();
// 5. 启动任务
env.execute("Socket WordCount");
}
// 自定义FlatMap函数
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("\\s+");
for (String word : words) {
if (word.length() > 0) {
out.collect(new Tuple2<>(word, 1));
}
}
}
}
}
4.4.2 运行步骤
- 启动Socket数据源:在终端执行
nc -lk 9999(Linux/Mac自带,Windows需要装ncat) - 运行Flink程序:在IDE里直接跑main方法,或者打成jar包提交到集群
- 输入数据:在nc窗口输入
hello world hello flink,观察控制台输出
4.4.3 输出示例
(hello,1)
(world,1)
(hello,2)
(flink,1)
你会看到,hello 出现了两次,计数累加到了2。这就是Flink的「有状态计算」——它记住了之前出现过的单词次数。
关键点:这里的 sum(1) 实际上是一个有状态算子。Flink内部维护了一个Map状态,key是单词,value是当前计数。每次新数据进来,都会更新这个状态。我在项目中遇到过状态无限增长的问题,后来加了TTL(Time-To-Live)才解决。
4.4.4 常见问题排查
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 连接Socket超时 | nc没启动或端口不对 | 检查 netstat -an | grep 9999 |
| 输出结果不累加 | 没有keyBy,直接sum了 | 确保keyBy指定了正确的key |
| 并行度与Slot不匹配 | Slot数小于并行度 | 调整 taskmanager.numberOfTaskSlots |
调试技巧:我经常在 flatMap 里加一行 System.out.println("收到数据: " + value),这样能快速确认数据是否到达了算子。生产环境当然不能用print,但本地调试时这招很管用。
好了,这一章的内容就是这些。你跟着把WordCount跑通,Flink的基础就算入门了。后面我们会在这个基础上,加入更复杂的清洗逻辑——比如过滤脏数据、字段映射、维表关联。那些才是ODS到DWD的核心战场。