4、Flink入门:Flink架构与核心组件、Flink开发环境搭建(本地模式)、第一个Flink WordCount程序

好,咱们正式进入 Flink 的世界。

这一章,我带你从零搭一套本地开发环境,再亲手跑起来第一个 WordCount 程序。别小看这个 WordCount,它背后藏着 Flink 最核心的架构思想。搞懂了它,后面学窗口、状态、容错都会顺很多。

4.1 Flink 架构与核心组件

先聊聊 Flink 到底长什么样。说白了,Flink 是一个「有状态的、针对无界/有界数据流」的分布式计算引擎。

我刚开始接触 Flink 时,最困惑的是它跟 Spark Streaming 到底有啥区别。后来在做一个实时风控项目时,发现 Flink 的「事件驱动」和「精确一次语义」确实更贴合业务需求。嗯,这里我简单拆解一下它的架构。

Flink 运行时架构(简化版)

一个 Flink 集群由三部分组成:Client、JobManager、TaskManager。数据流从 Source 进入,经过 Transformation,最后从 Sink 出去。

Flink 运行时核心架构 Client(客户端) JobManager(作业管理器) 调度、检查点协调、故障恢复 TaskManager 1 Slot 0 | Slot 1 TaskManager 2 Slot 0 | Slot 1 TaskManager 3 Slot 0 | Slot 1 Source(数据源) Transformation Sink(数据输出)

4.2 核心组件详解

咱们把上面图里的几个角色拆开看看。

组件 职责 我的经验
Client 提交作业、触发执行、获取执行结果 Client 本身不参与计算,提交完就可以退出
JobManager 作业调度、Checkpoint 协调、故障恢复 它是集群的「大脑」,挂了整个集群就瘫了
TaskManager 执行具体的 Task,管理 Slot 资源 每个 TaskManager 可以有多个 Slot,Slot 是资源隔离的最小单位
Slot TaskManager 内部的资源槽位,每个 Slot 运行一个 SubTask 我习惯把 Slot 数设成 CPU 核数,避免过度竞争

💡 小提示:Flink 的 Slot 是静态划分的,不像 Spark 那样动态申请。所以规划集群时,Slot 总数决定了你的并行度上限。

4.3 开发环境搭建(本地模式)

本地模式最简单,也最适合学习。你不需要搭集群,一个命令就能跑起来。

4.3.1 前置条件

  • JDK 8 或 11(我推荐 JDK 11,性能更好)
  • Maven 3.6+(用于构建项目)
  • IDE(IntelliJ IDEA 最佳,VSCode 也行)

4.3.2 下载与启动

  1. 去 Flink 官网下载最新稳定版(我写这章时是 1.17.x)
  2. 解压到本地目录,比如 /opt/flink-1.17.0
  3. 进入 bin/ 目录,执行 ./start-cluster.sh
  4. 打开浏览器访问 http://localhost:8081,看到 Web UI 就说明成功了

⚠️ 注意:本地模式默认使用 8081 端口。如果被占用,可以修改 conf/flink-conf.yaml 里的 rest.port 配置。

4.3.3 项目骨架

我习惯用 Maven 模板创建项目。你也可以直接复制下面的 pom.xml 核心依赖:

<dependencies>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-java</artifactId>
        <version>1.17.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-streaming-java</artifactId>
        <version>1.17.0</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-clients</artifactId>
        <version>1.17.0</version>
    </dependency>
</dependencies>

4.4 第一个 Flink WordCount 程序

终于到了动手环节。WordCount 是 Flink 的「Hello World」,但它的代码结构能让你看到 Flink 的完整执行链路。

4.4.1 代码实现

我直接贴一个完整的、可运行的版本。你复制到 IDE 里就能跑。

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 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(new Tokenizer())
            .keyBy(value -> value.f0)
            .sum(1);

        // 4. 输出结果
        counts.print();

        // 5. 启动执行
        env.execute("Flink 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));
                }
            }
        }
    }
}

4.4.2 运行步骤

  1. 在终端启动 netcat:nc -lk 9999
  2. 在 IDE 中运行 WordCount 的 main 方法
  3. 在 netcat 窗口输入一些英文单词,比如 hello world hello flink
  4. 观察 IDE 控制台输出:(hello,2) (world,1) (flink,1)

🔍 代码拆解:

  • StreamExecutionEnvironment:Flink 程序的入口,负责设置执行参数
  • socketTextStream:从 socket 读取数据流,这是 Source
  • flatMap:将一行文本拆成多个单词,每个单词变成 (word, 1)
  • keyBy:按单词分组,类似 SQL 的 GROUP BY
  • sum:对第二个字段求和,就是计数
  • print:将结果输出到标准输出,这是 Sink

4.4.3 避坑指南

⚠️ 我曾经踩过的坑:

  • 端口被占用:netcat 的 9999 端口如果被其他程序占用,Flink 会连不上。用 lsof -i :9999 检查一下。
  • 并行度问题:本地模式默认并行度是 CPU 核数。如果你用 print() 输出,多个线程的输出会交错,看起来乱。可以设置 env.setParallelism(1) 让输出有序。
  • 依赖冲突:Flink 自带的依赖跟项目里的版本不一致时,会报 NoClassDefFoundError。我建议把 flink-streaming-java 的 scope 设为 provided,避免打包时重复。

4.5 理解 Flink 的执行流程

跑通了 WordCount,咱们回头看看 Flink 到底是怎么处理这条数据流的。

WordCount 数据流执行流程 Source socketTextStream FlatMap 切分单词 KeyBy 按单词分组 Sum 累加计数 执行流程说明 1. Source 从 socket 读取一行文本,比如 "hello world" 2. FlatMap 拆成 (hello,1) (world,1) 3. KeyBy 将相同单词分到同一分区 4. Sum 对每个单词的计数累加,输出最终结果

你看,整个流程就是一条流水线。数据从 Source 流进来,经过每个算子处理,最后从 Sink 出去。每个算子都可以并行执行,这就是 Flink 高性能的秘密。

💡 我的习惯:刚开始学 Flink 时,我建议你多改改 WordCount 的代码。比如把 socketTextStream 换成 fromElements 读本地数据,或者把 print() 换成 writeAsText 写到文件。改一次,理解深一层。

好了,这一章的内容就到这。你搭好了环境,跑通了第一个程序,也看到了 Flink 的架构全貌。接下来,咱们会深入 Flink 的 DataStream API,看看怎么处理更复杂的实时逻辑。