Apache Flink 学习笔记 · Phase 1

基础筑基 |第 1--4 周

涵盖章节:Ch01 大数据生态与流处理思维 → Ch02 Flink 架构原理 → Ch03 环境搭建 → Ch04 时间语义与 Watermark


Ch01 · 大数据生态与流处理思维

1.1 为什么需要大数据处理?

传统数据库(MySQL、PostgreSQL)在单机数据量超过百亿条、并发写入超过万级时,性能会急剧下降。大数据技术栈的核心目标是:

  • 水平扩展:通过增加机器线性提升处理能力
  • 容错性:单节点故障不影响整体任务
  • 高吞吐:处理 TB 乃至 PB 级数据

1.2 批处理 vs 流处理

维度 批处理(Batch) 流处理(Streaming)
数据特征 有界(Bounded),数据是静止的 无界(Unbounded),数据持续流入
触发方式 手动或定时触发 事件驱动,持续运行
延迟 分钟级~小时级 毫秒级~秒级
代表框架 MapReduce、Hive Flink、Kafka Streams
典型场景 离线报表、模型训练 实时监控、风控、推荐

一句话记住:批处理是"先攒数据,再一起算";流处理是"数据来一条,处理一条"。

1.3 大数据架构演进

大数据架构经历了三个主要阶段:

Lambda 架构(2011年,Nathan Marz 提出)

同时维护批处理层(Batch Layer)和速度层(Speed Layer),用批结果修正流结果,但维护两套代码成本高。

Kappa 架构(2014年,Jay Kreps 提出)

只保留流处理层,通过重放历史数据替代批处理,简化架构,但对流处理框架要求极高。

流批一体(现代,Flink 主导)

一个框架同时支持高效的批处理和流处理,Flink 是目前最成熟的实现。
Lambda 架构 数据源 批处理层 速度层 合并服务层 ⚠ 双套代码,维护成本高 Kappa 架构 数据源 + 消息队列 统一流处理层 服务层 ✓ 单套代码,重放历史数据 流批一体(Flink) 有界/无界 数据源 Flink Runtime(统一引擎) 批结果 流结果 ★ 一套代码,同时支持批和流

复制代码
数据源层      →   传输层       →   计算层        →   存储/服务层
MySQL          Kafka            Apache Flink      Elasticsearch
日志文件        Pulsar           (批+流统一)        Redis
IoT 设备        RabbitMQ        (本课程主角)       HBase / HDFS
埋点数据                                           数据大屏
对比项 Apache Flink Spark Streaming
处理模型 真正的流处理(逐条) 微批处理(小批次)
延迟 毫秒级 秒级(批次间隔)
状态管理 原生支持,极其强大 较弱
Exactly-Once 原生支持 需额外配置
时间语义 事件时间原生支持 支持但不如Flink完善
SQL 支持 Flink SQL(非常完善) Spark SQL(以批为主)
适用场景 低延迟、复杂状态计算 大规模批处理、ML

Flink 采用经典的 主从架构(Master-Worker),主节点负责调度,工作节点负责执行。
Client 提交 Job 生成 JobGraph JobManager(主节点) Dispatcher 接受 Job 提交 JobMaster 管理单个 Job ResourceManager 管理 TaskManager Slot 资源 Checkpoint Coordinator 触发和协调全局 Checkpoint TaskManager 1 Slot 1 Slot 2 Slot 3 执行 Task(SubTask) 本地状态存储 TaskManager 2 Slot 1 Slot 2 Slot 3 执行 Task(SubTask) 本地状态存储 状态后端(HDFS/S3) 提交 分配Task

2.2 核心概念详解

JobManager(主节点) --- 大脑,负责:

  • Dispatcher:接收客户端提交的作业
  • JobMaster:每个 Job 对应一个,负责该 Job 的生命周期管理、Task 调度
  • ResourceManager:管理 TaskManager 的 Slot 资源
  • Checkpoint Coordinator:触发和协调全局一致性快照

TaskManager(工作节点) --- 执行单元,负责:

  • 接收并执行 JobManager 分配的 Task(SubTask)
  • 管理本地状态
  • 与相邻 TaskManager 进行数据交换(网络传输)

Slot(资源单位)

Slot 是 TaskManager 中最小的资源分配单位。每个 Slot 有固定的内存份额,但 CPU 不做硬性隔离。

复制代码
一个 TaskManager 有 3 个 Slot
→ 最多并行运行 3 个 SubTask
→ 多个 SubTask 可以共享同一个 Slot(通过 Slot Sharing)

2.3 Job 提交与执行流程

① 用户代码 DataStream API / Flink SQL ② StreamGraph 逻辑执行图 算子拓扑结构 ③ JobGraph 客户端生成 合并可链接算子 ④ ExecutionGraph JobManager 生成 并行化展开 ⑤ 物理执行 Task 部署至 TaskManager Slot Client 端 Client 端提交 JM 侧处理 TM 执行

2.4 算子链(Operator Chaining)

Flink 会将满足条件的相邻算子合并成一个 Task (即算子链),在同一线程内执行,避免跨网络的序列化开销

触发条件:

  1. 上下游算子并行度相同
  2. 下游算子只有一个输入源
  3. 没有跨 Slot 边界
java 复制代码
// 手动禁用算子链(调试时使用)
env.disableOperatorChaining();

// 在特定算子上断链
stream.map(...).startNewChain()

3.1 前置环境要求

软件 版本要求 说明
JDK 11 或 17(推荐) Flink 1.17+ 推荐 JDK 11
Maven 3.6+ 项目构建工具
IDE IntelliJ IDEA 推荐,Scala 插件可选
Flink 1.18.x(当前最新稳定) 本地开发模式,无需安装

注意:Flink 本地开发模式(Local Mode)不需要单独安装 Flink 服务,Maven 依赖会包含所有运行时组件。

3.2 Maven 项目创建

创建 pom.xml,引入 Flink 依赖:

xml 复制代码
<properties>
    <flink.version>1.18.1</flink.version>
    <java.version>11</java.version>
</properties>

<dependencies>
    <!-- Flink 流处理核心 -->
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-streaming-java</artifactId>
        <version>${flink.version}</version>
    </dependency>

    <!-- Flink 客户端(本地运行所需) -->
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-clients</artifactId>
        <version>${flink.version}</version>
    </dependency>

    <!-- 日志框架(必须,否则 Flink 报错) -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.36</version>
    </dependency>
</dependencies>

3.3 第一个程序:WordCount(批处理模式)

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 WordCount {
    public static void main(String[] args) throws Exception {
        // 1. 创建执行环境(本地模式,自动使用可用 CPU 核数)
        StreamExecutionEnvironment env = 
            StreamExecutionEnvironment.getExecutionEnvironment();

        // 2. 创建数据源(内存数据)
        DataStream<String> textStream = env.fromElements(
            "hello flink hello",
            "hello world",
            "flink is awesome"
        );

        // 3. 转换:切割 → 分组 → 求和
        DataStream<Tuple2<String, Integer>> wordCount = textStream
            .flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
                @Override
                public void flatMap(String line, Collector<Tuple2<String, Integer>> out) {
                    for (String word : line.split("\\s+")) {
                        out.collect(Tuple2.of(word, 1)); // 每个词输出 (word, 1)
                    }
                }
            })
            .keyBy(tuple -> tuple.f0)    // 按单词分组
            .sum(1);                      // 对第二个字段求和

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

        // 5. 触发执行(Flink 是懒执行,必须调用 execute)
        env.execute("WordCount Job");
    }
}

程序骨架理解(每个 Flink 程序必须包含):

复制代码
① 创建 StreamExecutionEnvironment   ← 入口,类似 Spark 的 SparkContext
② 添加 Source                       ← 数据从哪来
③ Transformations(链式操作)        ← 中间如何处理
④ 添加 Sink(或 print)              ← 结果去哪里
⑤ env.execute("任务名")             ← 必须!否则什么都不执行

3.4 Lambda 写法简化(Java 8+)

java 复制代码
// 等价写法,更简洁
DataStream<Tuple2<String, Integer>> wordCount = textStream
    .flatMap((String line, Collector<Tuple2<String, Integer>> out) -> {
        for (String word : line.split("\\s+")) {
            out.collect(Tuple2.of(word, 1));
        }
    })
    .returns(Types.TUPLE(Types.STRING, Types.INT))  // Lambda 需要手动指定返回类型
    .keyBy(t -> t.f0)
    .sum(1);

为什么 Lambda 需要 .returns()

Java 的类型擦除机制导致 Flink 无法推断泛型类型,必须手动声明,否则运行时报错。

3.5 预期输出

运行后控制台输出类似(顺序可能不同):

复制代码
(hello,3)
(flink,2)
(world,1)
(is,1)
(awesome,1)

Ch04 · 时间语义与 Watermark 机制

这是 Flink 中最重要、最容易混淆的概念,请反复理解。

4.1 三种时间语义

← 数据流时间轴 → 事件A t=09:00:01 事件B t=09:00:05 事件C(迟到) 实际t=09:00:03 事件D t=09:00:08 事件时间(Event Time) ★ 推荐使用 • 数据产生时自带的时间戳 • 事件C虽然迟到,但按 09:00:03 处理(正确!) • 结果可重现,语义最准确 ✓ 用于生产环境 处理时间(Processing Time) 最简单 • 数据到达算子时的系统时间 • 事件C按实际到达时间处理 • 不需要 Watermark • 结果不可重现,受系统影响 △ 用于对准确性要求不高的场景 摄入时间(Ingestion Time) 折中方案 • 数据进入 Flink Source 的时间 • 介于两者之间 • 自动分配时间戳 • 现实中较少使用 △ 了解即可

java 复制代码
// 设置时间语义(Flink 1.12+ 默认已是 Event Time,无需额外设置)
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 默认就是 EventTime,以下两行仅作说明

4.2 为什么需要 Watermark?

在实际网络环境中,数据到达的顺序不等于事件发生的顺序

  • 网络抖动、消息队列分区乱序等原因导致事件迟到
  • 如果一直等待迟到数据,窗口永远无法触发计算
  • 如果不等待,则会漏掉迟到的数据

Watermark 的本质:一个随数据流传递的特殊标记,含义是:

"我承诺,时间戳 ≤ Watermark 值的数据已经全部到达,可以触发计算了。"

4.3 Watermark 工作原理(核心!)

Watermark 触发窗口计算示意(允许最大乱序 2秒,窗口大小 5秒) time :00 :01 :02 :03 :04 :05 t=:00 事件1 t=:02 事件2 t=:01 事件3(迟到) t=:04 事件4 t=:06 事件5 →触发! WM = max(4) - 2 = 2 → WM=:02 WM = max(6) - 2 = 4 → WM=:04 窗口 [00:00, 00:05) → Watermark ≥ 5 时触发 当 WM 推进到 5 时(即事件时间戳 ≥ 7,减去延迟2),窗口关闭并触发计算 Watermark 计算公式 Watermark(t) = max(已观察到的事件时间戳) - 允许的最大乱序时间 窗口触发条件:Watermark ≥ 窗口结束时间

4.4 代码实现

方式一:固定延迟 Watermark(最常用)

java 复制代码
DataStream<MyEvent> stream = env.addSource(source);

DataStream<MyEvent> withWatermarks = stream
    .assignTimestampsAndWatermarks(
        WatermarkStrategy
            // 允许最大 3 秒的乱序
            .<MyEvent>forBoundedOutOfOrderness(Duration.ofSeconds(3))
            // 告诉 Flink 如何从事件中提取时间戳
            .withTimestampAssigner((event, recordTimestamp) -> event.getTimestamp())
    );

方式二:自定义 Watermark 策略

java 复制代码
WatermarkStrategy<MyEvent> strategy = new WatermarkStrategy<MyEvent>() {
    @Override
    public WatermarkGenerator<MyEvent> createWatermarkGenerator(
            WatermarkGeneratorSupplier.Context context) {
        return new WatermarkGenerator<MyEvent>() {
            private long maxTimestamp = Long.MIN_VALUE;
            private final long maxOutOfOrderness = 3000L; // 3秒

            @Override
            public void onEvent(MyEvent event, long eventTimestamp, 
                                WatermarkOutput output) {
                // 每条数据到来时更新最大时间戳
                maxTimestamp = Math.max(maxTimestamp, eventTimestamp);
            }

            @Override
            public void onPeriodicEmit(WatermarkOutput output) {
                // 周期性发出 Watermark(默认200ms一次)
                output.emitWatermark(new Watermark(maxTimestamp - maxOutOfOrderness - 1));
            }
        };
    }
};

配置 Watermark 发出间隔(默认200ms):

java 复制代码
env.getConfig().setAutoWatermarkInterval(200); // 毫秒

4.5 处理真正迟到的数据

即使设置了 Watermark,还是可能有极端迟到的数据(超过允许的乱序时间)。Flink 提供了三层处理机制:

java 复制代码
DataStream<MyEvent> result = withWatermarks
    .keyBy(e -> e.getKey())
    .window(TumblingEventTimeWindows.of(Time.minutes(1)))
    // 第二层:窗口关闭后允许继续等待 30 秒
    .allowedLateness(Time.seconds(30))
    // 第三层:超过 allowedLateness 的极端迟到数据,输出到侧流
    .sideOutputLateData(lateOutputTag)
    .sum("value");

// 获取极端迟到数据
DataStream<MyEvent> lateData = result.getSideOutput(lateOutputTag);

第一层 Watermark 延迟 允许一定程度乱序 forBoundedOutOfOrderness 正常乱序数据 → 正确处理 → 第二层 allowedLateness 窗口关闭后继续等待 迟到数据触发窗口更新 轻度迟到数据 → 重算窗口 → 第三层 SideOutput 极端迟到数据 导流至侧输出流 极端迟到 → 侧流处理

4.6 本章知识点总结

概念 核心要点
Event Time 事件产生时的时间戳,最准确,生产首选
Processing Time 系统当前时间,最简单,结果不可重现
Watermark 用于推进事件时间,解决乱序问题的核心机制
Watermark 公式 max_timestamp - max_out_of_orderness
窗口触发条件 Watermark ≥ 窗口结束时间
allowedLateness 窗口关闭后继续接受迟到数据的等待时间
SideOutput 处理超过 allowedLateness 的极端迟到数据

Phase 1 · 阶段检验

完成本阶段学习后,你应该能够:

  • 清晰说出流处理和批处理的核心差异,以及 Flink 解决的核心问题
  • 徒手画出 Flink JobManager / TaskManager / Slot 的关系图
  • 解释 Slot 和并行度的关系(一个 Task 需要多少 Slot?)
  • 独立搭建 Flink Maven 项目并运行 WordCount
  • 解释 Watermark 的推进逻辑,说清楚窗口何时触发

下一章:Phase 2 · Ch05 Source 与 Sink:数据的入口与出口

相关推荐
白眼黑刺猬2 小时前
真实面试:大数据开发岗
大数据·面试·职场和发展
D愿你归来仍是少年2 小时前
Apache Spark 第 13 章:Real-Time Mode 实时计算
大数据·spark·apache
源码之家2 小时前
计算机毕业设计:基于Python的二手车数据分析可视化系统 Flask框架 可视化 时间序列预测算法 逻辑回归 requests 爬虫 大数据(建议收藏)✅
大数据·hadoop·python·算法·数据分析·flask·课程设计
昨夜见军贴06162 小时前
AI报告文档审核赋能数据不出域:IACheck重构机械制造行业本地化质量管控体系
大数据·人工智能·重构
炜宏资料库2 小时前
华为五级流程体系(L1-L5) 、流程框架、实施方法与最佳实践108页PPT
大数据·华为
源码之屋2 小时前
计算机毕业设计:新能源汽车多维度数据分析系统 Django框架 Scrapy爬虫 可视化 数据分析 大数据 大模型 机器学习(建议收藏)✅
大数据·python·scrapy·django·汽车·课程设计·美食
sthnyph2 小时前
防火墙安全策略(基本配置)
服务器·php·apache
ACGkaka_2 小时前
ES 学习(五):DSL常用操作整理
大数据·学习·elasticsearch
CDA数据分析师干货分享2 小时前
统计学本科生CDA数据分析师二级备考经验分享
大数据·人工智能·经验分享·数据分析·cda证书·cda数据分析师