2. Flink 核心概念与架构

这一章,我们来啃 Flink 最核心的骨架。说实话,很多运维同学把 Flink 当成一个黑盒,任务挂了重启,数据延迟了加资源。但如果你不理解它的数据流模型和运行时架构,遇到复杂故障时基本只能靠猜。我个人习惯,学任何系统先看它的数据模型——它决定了你能做什么、不能做什么。

2.1 Flink 数据流模型

Flink 的数据流模型,说白了就是「一切皆流」。它不像 Spark 那样把数据切成一批批的微批次,而是每来一条数据就处理一条。这个区别,在实时场景下是致命的。

2.1.1 数据流与转换

Flink 把数据抽象成 DataStream,你可以对它做各种转换操作。我画了一张图,帮你快速建立整体认知:

Flink 数据流模型核心流程 数据源 Kafka / Socket / 文件 DataStream 无界数据流 转换操作 map / filter / keyBy window / join Sink 状态管理 (State) ValueState / ListState / MapState 容错机制 (Checkpoint) 分布式快照 / 精确一次语义 💡 核心要点 • 数据流有界/无界 • 转换操作延迟低 • 状态与流共存

数据源可以是 Kafka、Socket 或者文件。Flink 把它们统一抽象成 DataStream,然后你可以在上面做各种转换。这里有个关键点:数据流分为有界和无界。有界流就是批处理,无界流才是真正的流处理。

我的经验: 刚开始接触 Flink 时,我总把有界流和无界流混着用。后来在做一个实时大屏项目时,因为没区分清楚,导致窗口计算一直不触发。排查了半天才发现——数据源配成了有界流,数据跑完任务就停了。嗯,从那以后我每次建任务都会先确认数据源类型。

2.1.2 窗口与时间语义

流处理里,窗口是个绕不开的概念。数据是源源不断的,但计算往往需要在一个「时间段」或「事件数量」内聚合。Flink 提供了三种窗口:

窗口类型 说明 适用场景
Tumbling Window 固定大小,不重叠 每分钟统计 PV
Sliding Window 固定大小,可重叠 每 5 秒统计最近 1 分钟数据
Session Window 基于不活动间隔 用户行为会话分析

时间语义这块,我建议你重点理解 Event Time。Processing Time 虽然简单,但在生产环境里基本不靠谱。为什么?因为数据到达顺序是乱的,网络延迟、背压都会导致时间错乱。

避坑指南: 我曾经在一个金融风控项目里,默认用了 Processing Time。结果数据因为网络抖动延迟了 10 秒,窗口计算全乱套了。后来改成 Event Time + Watermark,才解决了乱序问题。记住:生产环境一定要用 Event Time,除非你的场景对延迟极度敏感且数据有序。

2.2 运行时架构

理解了数据流模型,我们再来看 Flink 是怎么跑起来的。运行时架构就两个核心角色:JobManager 和 TaskManager。我习惯把它们比作「项目经理」和「一线工人」。

2.2.1 JobManager

JobManager 是集群的「大脑」。它负责接收作业、调度任务、协调 Checkpoint。在 Flink 1.9 之后,JobManager 支持 Standalone 和 Active-Passive 两种高可用模式。

JobManager 内部有三个关键组件:

  • ResourceManager:管理资源,向 TaskManager 申请 Slot
  • Dispatcher:接收作业提交,启动 JobMaster
  • JobMaster:每个作业一个,负责调度和 Checkpoint

运维要点: JobManager 的 JVM 参数要格外注意。我见过太多人把 JobManager 堆内存设得和 TaskManager 一样大,结果资源全浪费了。JobManager 主要做调度和元数据管理,2-4GB 堆内存通常就够了,除非你同时跑几十个作业。

2.2.2 TaskManager

TaskManager 是真正干活的。每个 TaskManager 有多个 Slot,Slot 是资源调度的最小单位。一个 Slot 可以运行一个 SubTask。

这里有个容易混淆的点:Slot 隔离的是内存,不是 CPU。CPU 是共享的。所以如果你有两个 CPU 密集型的任务放在同一个 TaskManager 上,它们会抢 CPU。

配置项 建议值 说明
taskmanager.numberOfTaskSlots 4-8 根据 CPU 核数设定
taskmanager.memory.process.size 4-16GB 总内存,包含堆外内存
taskmanager.memory.managed.size 总内存的 40% 用于排序、哈希等操作
我的习惯: 我会把 Slot 数设成 CPU 核数的一半。比如 8 核机器设 4 个 Slot。这样每个任务至少有 2 个核可用,避免 CPU 争抢。当然,如果你的任务主要是 IO 密集型,可以适当增加 Slot 数。

2.3 状态管理与容错机制

状态管理是 Flink 区别于其他流处理引擎的核心能力。没有状态,流处理就只能做简单的过滤和转换。有了状态,你才能做累加、去重、Join 这些复杂操作。

2.3.1 状态类型

Flink 提供了两种状态:Managed State 和 Raw State。生产环境几乎只用 Managed State,因为它自动管理存储和容错。

Managed State 又分三种:

  • ValueState:单个值,比如计数器
  • ListState:列表,比如缓存最近 100 条记录
  • MapState:键值对,比如用户画像

状态后端决定了状态存在哪里。Flink 支持三种:

状态后端 存储位置 适用场景
MemoryStateBackend TaskManager 内存 开发测试,状态小
FsStateBackend TaskManager 内存 + 文件系统 生产环境,状态中等
RocksDBStateBackend RocksDB(本地磁盘) 生产环境,状态大(GB 级)
避坑指南: 我曾经在生产环境用了 MemoryStateBackend,状态只有几百 MB,觉得没问题。结果一次 GC 导致 Full GC 长达 10 秒,Checkpoint 超时,任务重启。后来换成 RocksDBStateBackend,虽然读写慢一点,但稳定多了。记住:生产环境默认用 RocksDB,除非你确认状态很小且 GC 可控。

2.3.2 Checkpoint 与 Savepoint

容错机制的核心是 Checkpoint。Flink 通过分布式快照,定期保存所有算子的状态。一旦任务失败,就从最近的 Checkpoint 恢复。

Checkpoint 的关键配置:

  • interval:间隔时间,默认 10 秒。太短影响性能,太长恢复慢
  • timeout:超时时间,默认 10 分钟。超过就丢弃
  • minPauseBetweenCheckpoints:最小间隔,防止频繁触发

Savepoint 和 Checkpoint 很像,但 Savepoint 是手动触发的,用于版本升级、作业迁移。我建议每次上线前都手动打一个 Savepoint。

运维口诀: Checkpoint 保命,Savepoint 保升级。Checkpoint 自动跑,Savepoint 手动打。两者都用 RocksDB 后端,别混用。

2.3.3 精确一次语义

Flink 号称支持「精确一次」语义。但说实话,这个「精确一次」是有条件的。它依赖于 Checkpoint 和 Kafka 等 Source/Sink 的幂等性。

实现原理:

  1. Source 记录当前消费的偏移量到状态中
  2. Checkpoint 时,将偏移量和算子状态一起快照
  3. 恢复时,从 Checkpoint 读取偏移量,重新消费
  4. Sink 端需要支持幂等写入或事务写入
我的经验: 精确一次在大多数场景下是「几乎精确一次」。比如 Kafka 到 Kafka 的管道,如果 Sink 端写入成功但 Checkpoint 失败,重启后数据会重复。所以别太迷信「精确一次」,做好去重才是王道。我通常会在业务层加一个去重逻辑,用唯一 ID 做幂等。

最后,我画了一张运行时架构图,帮你把 JobManager、TaskManager、状态后端和 Checkpoint 串起来:

Flink 运行时架构 JobManager 调度 / Checkpoint 协调 TaskManager 1 Slot 0 | Slot 1 | Slot 2 TaskManager 2 Slot 0 | Slot 1 | Slot 2 TaskManager 3 Slot 0 | Slot 1 | Slot 2 RocksDB 状态后端 本地磁盘 Checkpoint / Savepoint HDFS / S3 💡 JobManager 调度 TaskManager,TaskManager 通过 RocksDB 管理状态,Checkpoint 定期将状态快照到远程存储

这一章的内容就到这里。数据流模型、运行时架构、状态管理和容错机制,是 Flink 运维的三大基石。你把这些搞透了,后面遇到性能调优、故障排查才不会慌。