第3章:Flink基础入门:Flink架构、DataStream API概览、Flink开发环境搭建(Maven/SBT)
好,咱们进入第三章。这一章我打算带大家把Flink的底裤扒开看看——不是真的扒,而是从架构层面理解它为什么这么快、这么稳。然后咱们再上手搭环境,写第一个DataStream程序。嗯,这部分内容我当年自学时踩了不少坑,今天一并分享出来。
3.1 Flink架构:从“流水线”到“分布式引擎”
先问大家一个问题:你觉得一个实时计算引擎,最核心要解决什么问题?
我个人理解,就两个:数据不丢、算得够快。Flink的架构设计,说白了就是围绕这两点展开的。
3.1.1 角色划分:JobManager vs TaskManager
Flink集群里有两个关键角色:
- JobManager:大脑。负责调度任务、协调检查点、处理故障恢复。你可以把它想象成工地上的包工头,只管派活和验收,不亲自搬砖。
- TaskManager:苦力。真正干活的节点,每个TaskManager里有多个Task Slot(任务槽),一个Slot就是一个线程执行单元。
我记得第一次部署Flink集群时,我犯了个低级错误——把JobManager和TaskManager部署在同一台机器上。结果任务一多,JobManager直接OOM了。后来才明白,生产环境一定要分开部署,JobManager的机器要留足内存给元数据和检查点管理。
3.1.2 数据流动:从Source到Sink
Flink程序的基本结构,其实就三块:
- Source:数据源。比如Kafka、Socket、文件系统。
- Transformation:转换操作。比如map、filter、window。
- Sink:输出目的地。比如Kafka、MySQL、HDFS。
你想想看,这不就是一条流水线吗?数据从源头流进来,经过一道道工序加工,最后打包出货。Flink的牛逼之处在于,它能让这条流水线在成百上千台机器上并行跑,还能保证数据不乱序、不丢失。
3.2 DataStream API概览:你写的第一个“Hello World”
DataStream API是Flink最核心的编程接口。说白了,它就是一套让你能操作“数据流”的API。我习惯把它分成三类操作:
| 操作类型 | 常用算子 | 说明 |
|---|---|---|
| 基础转换 | map, flatMap, filter | 一对一或一对多的元素处理 |
| 分组聚合 | keyBy, sum, reduce | 按key分组后做聚合计算 |
| 窗口操作 | window, windowAll | 按时间或数量划分窗口 |
这里我特别想强调一点:DataStream API是延迟执行的。什么意思?就是你写了一堆map、filter,其实它们只是构建了一个执行计划,直到你调用execute(),Flink才会真正开始跑。这个设计跟Spark的懒加载很像,好处是Flink可以优化整个执行图。
.print()算子,它会把数据直接输出到控制台。但注意,生产环境千万别用,否则日志会爆炸。我曾经有个同事在生产代码里忘了删print,结果TaskManager的日志一天涨了200GB...
3.3 Flink开发环境搭建:Maven/SBT实战
好,理论说完了,咱们动手。搭建Flink开发环境,我推荐两种方式:Maven(Java/Scala)和SBT(Scala)。我个人更习惯Maven,因为生态更成熟。
3.3.1 使用Maven创建Flink项目
第一步,用Maven Archetype生成项目骨架:
mvn archetype:generate \
-DarchetypeGroupId=org.apache.flink \
-DarchetypeArtifactId=flink-quickstart-java \
-DarchetypeVersion=1.17.0 \
-DgroupId=com.example \
-DartifactId=flink-demo \
-Dversion=1.0-SNAPSHOT
这个命令会生成一个标准的Flink项目,里面自带一个StreamingJob.java示例。嗯,这里要注意:Archetype版本一定要跟你安装的Flink集群版本一致。我曾经因为版本不匹配,折腾了半天才发现是客户端和服务器版本对不上。
3.3.2 核心依赖配置
生成项目后,打开pom.xml,核心依赖就这几个:
<dependencies>
<!-- Flink核心依赖 -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java</artifactId>
<version>1.17.0</version>
<scope>provided</scope>
</dependency>
<!-- 如果要用Kafka连接器 -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka</artifactId>
<version>1.17.0</version>
</dependency>
</dependencies>
scope=provided表示这个依赖在运行时由Flink集群提供,打包时不会包含。这样可以减小JAR包体积。但如果你是在本地IDE里跑测试,记得改成compile,否则会报ClassNotFoundException。
3.3.3 第一个DataStream程序:单词计数
来,写一个最简单的单词计数程序。这个例子我每次培训都会讲,因为它麻雀虽小五脏俱全:
public class WordCount {
public static void main(String[] args) throws Exception {
// 1. 创建执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 2. 从socket读取数据(本地测试用)
DataStream<String> text = env.socketTextStream("localhost", 9999);
// 3. 转换:切分单词,计数
DataStream<Tuple2<String, Integer>> counts = text
.flatMap((String line, Collector<Tuple2<String, Integer>> out) -> {
for (String word : line.split(" ")) {
out.collect(new Tuple2<>(word, 1));
}
})
.keyBy(value -> value.f0)
.sum(1);
// 4. 打印结果
counts.print();
// 5. 执行
env.execute("Socket WordCount");
}
}
这段代码的逻辑很直白:从socket读一行文本,按空格切分成单词,然后按单词分组,累加次数。最后打印到控制台。
你可能会问:为什么要用flatMap而不是map?因为一行文本会切出多个单词,这是一对多的关系,flatMap正好合适。我刚开始学的时候也搞混过,后来记住一个口诀:一对一用map,一对多用flatMap。
3.3.4 使用SBT(Scala用户)
如果你用Scala,SBT是更自然的选择。在build.sbt里这样配:
name := "flink-scala-demo"
version := "1.0"
scalaVersion := "2.12.17"
libraryDependencies ++= Seq(
"org.apache.flink" %% "flink-streaming-scala" % "1.17.0" % "provided",
"org.apache.flink" % "flink-connector-kafka" % "1.17.0"
)
注意这里用了%%,SBT会自动匹配Scala版本的后缀。比如flink-streaming-scala_2.12。这个细节我当年没注意,手动写版本号写错了,编译报错查了半天。
3.4 本地运行与调试
写完代码怎么跑?我一般分两步:
- 本地IDE直接运行:Flink会自动启动一个嵌入式集群,适合调试。
- 打包提交到集群:用
mvn clean package打成JAR,然后flink run -c com.example.WordCount target/flink-demo-1.0-SNAPSHOT.jar。
本地运行时,记得先启动一个socket数据源:
nc -lk 9999
然后在终端里输入单词,你就能在IDE的控制台看到实时输出的计数结果了。嗯,第一次看到数据流起来的时候,还是挺有成就感的。
ClassNotFoundException,八成是依赖scope设成了provided。临时改成compile就行,提交到集群前再改回来。
3.5 本章小结
这一章我们干了三件事:理解了Flink的架构(JobManager/TaskManager)、熟悉了DataStream API的核心算子、亲手搭了开发环境并跑通了第一个程序。说实话,Flink入门并不难,难的是后面处理乱序数据、状态管理、检查点这些进阶内容。不过别急,咱们一章一章来。
下一章,我会带大家深入DataStream API的细节,重点讲时间语义和Watermark——这可是实时计算的灵魂。到时候我会分享一个我在金融风控项目中遇到的乱序数据坑,保证让你印象深刻。
flink-connector-kafka依赖,并配置Kafka的bootstrap.servers。
好,今天就到这儿。有问题欢迎在课程群里讨论,或者直接找我。咱们下章见。