2. Flink 环境搭建与项目初始化

本章将引导你完成从零搭建 Flink 开发环境、理解集群部署模式、创建 Maven/Gradle 项目并运行第一个 Flink 程序的全过程。这是后续所有批流一体特征工程案例的基础。

2.1 本地开发环境配置

推荐使用以下工具链进行 Flink 应用开发:

组件 推荐版本 说明
JDK 1.8 / 11 (LTS) Flink 1.14+ 对 Java 11 支持更佳
Maven 3.6.3+ 项目构建与依赖管理
IntelliJ IDEA 2022.3+ 社区版或 Ultimate 版均可
Git 2.30+ 版本控制
Docker (可选) 20.10+ 本地快速启动 Flink 集群
环境检查: 在终端执行 java -version、mvn -version 确认安装成功。确保 JAVA_HOME 环境变量已正确设置。

2.2 Flink 集群模式介绍

Flink 支持多种部署模式,适用于不同规模和场景。以下是三种核心模式的对比:

模式 资源管理 适用场景 特点
Local 单 JVM 进程 本地开发、调试、单元测试 无需集群,开箱即用
Standalone Flink 自带 小规模测试、学习环境 部署简单,无外部依赖
YARN / Kubernetes YARN / K8s 生产环境、大规模作业 资源弹性、高可用、与大数据生态集成
课程选择: 本课程所有案例均基于 Local 模式 进行开发和验证,确保读者无需复杂集群即可上手。生产部署部分将在后续章节单独讲解。
Flink 集群部署模式架构对比 Local 模式 JobManager TaskManager 单 JVM 进程 开发调试专用 Standalone 模式 JobManager (HA) TaskManager * N 独立集群 小规模测试 YARN / K8s 模式 JobManager TaskManager * N YARN / K8s 资源 生产环境大规模作业 说明:Local 模式将 JobManager 和 TaskManager 合并到同一 JVM,适合开发;Standalone 和 YARN/K8s 模式支持分布式部署。

2.3 Maven/Gradle 项目搭建

我们提供两种主流构建工具的快速初始化方式。推荐使用 Maven 作为本课程的标准构建工具。

2.3.1 使用 Maven 创建项目

在终端执行以下命令,或使用 IntelliJ IDEA 的 Maven 项目向导:

mvn archetype:generate \
    -DarchetypeGroupId=org.apache.flink \
    -DarchetypeArtifactId=flink-quickstart-java \
    -DarchetypeVersion=1.17.1 \
    -DgroupId=com.featureeng \
    -DartifactId=flink-feature-engineering \
    -Dversion=1.0-SNAPSHOT \
    -Dpackage=com.featureeng \
    -DinteractiveMode=false

生成的项目结构如下:

flink-feature-engineering/
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       └── featureeng/
│   │   │           ├── BatchJob.java
│   │   │           ├── StreamingJob.java
│   │   │           └── SocketWindowWordCount.java
│   │   └── resources/
│   │       └── log4j.properties
│   └── test/
│       └── java/
│           └── com/
│               └── featureeng/
│                   └── BatchJobTest.java

2.3.2 使用 Gradle 创建项目

对于偏好 Gradle 的开发者,在 build.gradle 中添加以下依赖:

plugins {
    id 'java'
    id 'application'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.apache.flink:flink-java:1.17.1'
    implementation 'org.apache.flink:flink-streaming-java:1.17.1'
    implementation 'org.apache.flink:flink-clients:1.17.1'
}

application {
    mainClass = 'com.featureeng.StreamingJob'
}
版本一致性: 确保所有 Flink 依赖的版本号完全一致(如 1.17.1),避免因版本冲突导致运行时异常。

2.4 第一个 Flink 程序:批流一体 WordCount

我们编写一个同时支持批处理和流处理的 WordCount 程序,展示 Flink 批流一体的核心能力。

2.4.1 批处理版本 (BatchJob.java)

import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.DataSet;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.util.Collector;

public class BatchJob {
    public static void main(String[] args) throws Exception {
        // 1. 创建批处理执行环境
        final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

        // 2. 读取数据源
        DataSet<String> text = env.fromElements(
            "Hello Flink", "Hello World", "Flink Feature Engineering");

        // 3. 转换:分词 & 计数
        DataSet<Tuple2<String, Integer>> counts = text
            .flatMap(new Tokenizer())
            .groupBy(0)
            .sum(1);

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

    public static class Tokenizer implements FlatMapFunction<String, Tuple2<String, Integer>> {
        @Override
        public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {
            String[] tokens = value.toLowerCase().split("\\W+");
            for (String token : tokens) {
                if (token.length() > 0) {
                    out.collect(new Tuple2<>(token, 1));
                }
            }
        }
    }
}

2.4.2 流处理版本 (StreamingJob.java)

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 StreamingJob {
    public static void main(String[] args) throws Exception {
        // 1. 创建流处理执行环境
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 2. 读取 socket 数据流 (需先启动 nc -lk 9999)
        DataStream<String> text = env.socketTextStream("localhost", 9999);

        // 3. 转换:分词 & 计数
        DataStream<Tuple2<String, Integer>> counts = text
            .flatMap(new Tokenizer())
            .keyBy(0)
            .sum(1);

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

        // 5. 执行作业
        env.execute("Streaming WordCount");
    }

    public static class Tokenizer implements FlatMapFunction<String, Tuple2<String, Integer>> {
        @Override
        public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {
            String[] tokens = value.toLowerCase().split("\\W+");
            for (String token : tokens) {
                if (token.length() > 0) {
                    out.collect(new Tuple2<>(token, 1));
                }
            }
        }
    }
}
第一个 Flink 程序:批流一体 WordCount 执行流程 数据源 fromElements() flatMap → groupBy → sum print() socketTextStream() flatMap → keyBy → sum print() → execute() 说明:批处理使用 DataSet API (groupBy),流处理使用 DataStream API (keyBy),但核心业务逻辑 Tokenizer 完全复用。

2.5 运行与验证

  1. 批处理运行: 在 IDE 中直接运行 BatchJob.main(),控制台将输出单词统计结果。
  2. 流处理运行:
    • 先启动 socket 数据源:nc -lk 9999
    • 再运行 StreamingJob.main()
    • 在 nc 终端输入文本,观察 Flink 控制台实时输出计数结果。
关键观察: 批处理和流处理的核心转换逻辑(Tokenizer 类)完全一致,仅执行环境(ExecutionEnvironment vs StreamExecutionEnvironment)和分组算子(groupBy vs keyBy)不同。这正是 Flink 批流一体架构的直观体现——同一套代码,两种执行模式。

2.6 常见问题与避坑指南

问题 原因 解决方案
ClassNotFoundException 依赖版本不一致或缺失 检查 pom.xml 中 flink-java、flink-streaming-java 版本是否一致
NoClassDefFoundError 缺少 flink-clients 依赖 添加 flink-clients 依赖到 pom.xml
Socket 连接失败 未启动 nc 或端口被占用 执行 nc -lk 9999 或更换端口
中文乱码 IDE 编码设置问题 设置 IDE 文件编码为 UTF-8
快速验证: 如果不想启动 socket,可以将流处理的数据源改为 env.fromElements(...) 进行本地测试,与批处理共享同一套数据准备逻辑。