一、引言
Apache Flink 是面向有界与无界数据流的分布式、有状态计算框架。它以流处理为基础,通过事件时间、Watermark、分布式状态、Checkpoint 和输入重放等机制,支持实时分析、持续 ETL、规则检测、CDC 与实时数仓等场景。
yaml
数据处理需求的演进
离线报表
|
v
MapReduce:吞吐优先、阶段式批处理
|
+----------------------------+
| |
v v
更低处理延迟 更复杂业务语义
| |
| 乱序、状态、窗口
| 故障恢复、持续计算
+-------------+--------------+
|
v
有状态流处理系统
|
v
Apache Flink
二、Flink 的核心定位
Apache Flink 定义为:在 无界和有界数据流上执行有状态计算 的框架与分布式处理引擎。
这里包含三个核心概念:
- 无界流 :只有起点,没有预先确定的终点,例如 Kafka 中持续产生的订单事件。
- 有界流 :具有明确的起点和终点,例如对象存储中的一批历史日志。
- 有状态计算 :当前记录的处理结果依赖此前数据,例如累计金额、设备当前状态或用户最近行为。
Flink 的基础思想常被概括为:批处理是有界流处理,实时处理是无界流处理。但"流批统一"不等于流任务与批任务采用完全相同的物理执行方式。Flink 在统一的数据流模型和 API 之上处理有界与无界输入,同时根据输入是否有界选择不同的调度、Shuffle、Join、状态和故障恢复策略。
sql
Apache Flink 统一数据流模型
无界数据流 有界数据流
Kafka / CDC / 设备事件 文件 / 历史快照 / 批量导入
| |
v v
STREAMING 执行模式 BATCH 执行模式
持续运行、流水线传输 阶段调度、中间结果物化
Checkpoint 恢复 批式故障恢复
| |
+------------------+------------------+
|
v
DataStream / Table API / SQL
|
v
Flink Runtime
STREAMING 模式也可以处理有界数据; BATCH 模式只能处理所有 Source 都有界的作业。两者应得到相同的最终业务结果,但中间输出、资源占用和恢复方式可能不同。
三、运行时架构
一个 Flink 集群主要由 JobManager 和 TaskManager 两类进程组成。提交作业的 Client 负责构建并提交数据流,但它不属于运行时本身,提交完成后可以断开。
lua
+--------------------+
| Client |
| 构建并提交 JobGraph |
+----------+---------+
|
v
+----------------------------------------------------------------+
| JobManager |
| |
| +----------------+ +----------------+ +-------------------+ |
| | Dispatcher | | ResourceManager| | JobMaster | |
| | REST / Web UI | | Slot 与资源协调 | | 单个作业调度与恢复 | |
| +----------------+ +----------------+ +-------------------+ |
+-------------------------------+--------------------------------+
|
调度 Task / 协调 Checkpoint
|
+-----------------+-----------------+
| |
v v
+-----------------------------+ +-----------------------------+
| TaskManager 1 | | TaskManager 2 |
| | | |
| Slot 0: Source -> Map |<--->| Slot 0: KeyBy -> Window |
| Slot 1: Sink | | Slot 1: Aggregate -> Sink |
| | | |
| 状态、网络缓冲、数据交换 | | 状态、网络缓冲、数据交换 |
+-----------------------------+ +-----------------------------+
3.1.JobManager
JobManager 是集群的协调者,主要负责:
- 接收和管理作业;
- 调度 Task;
- 协调 Checkpoint;
- 处理 Task 失败并触发恢复;
- 向资源管理平台申请或释放资源;
- 维护作业执行状态。
从职责上看,JobManager 内部包含三个重要组件:
| 组件 | 主要职责 |
|---|---|
| Dispatcher | 接收作业,提供 REST 接口和 Web UI,为作业启动 JobMaster |
| ResourceManager | 管理 Task Slot,并对接 Standalone、YARN 或 Kubernetes |
| JobMaster | 管理某一个具体作业的调度、状态和故障恢复 |
3.2.TaskManager
askManager 是执行实际计算的 Worker 进程,负责运行 Task、保存本地状态、缓存数据并与其他 TaskManager 交换数据。每个 TaskManager 可以提供一个或多个 Task Slot ,Slot 是 Flink 调度资源的基本单位。
需要注意:
- Slot 不等于 CPU Core;
- Slot 目前主要用于划分和隔离 Managed Memory,不提供严格的 CPU 隔离;
- 一个 Slot 不一定只运行一个算子;
- Operator Chaining 和 Slot Sharing 可以让多个算子或 Subtask 共享 Slot。
四、Flink作业提交执行流程
DataStream 程序从用户编写的 API 调用到实际执行,需要经过多层图结构转换。
yaml
DataStream API
|
v
Transformation DAG
|
| StreamGraphGenerator
v
StreamGraph
|
| 算子链、物理边、Slot Sharing
v
JobGraph
|
| 按并行度展开
v
ExecutionGraph
|
v
Task / Subtask
|
v
TaskManager 执行
各层图的作用如下:
| 图结构 | 含义 |
|---|---|
| Transformation DAG | 用户 API 所声明的转换关系 |
| StreamGraph | DataStream 拓扑的中间表示 |
| JobGraph | 提交给 JobManager 的逻辑执行图 |
| ExecutionGraph | 按并行度展开、用于调度和跟踪运行状态的图 |
| Task/Subtask | 最终部署到 TaskManager 的执行实例 |
例如,一个并行度为 100 的 JobVertex ,可能在 ExecutionGraph 中展开为 100 个 ExecutionVertex 。相邻且满足条件的算子还可以被合并为 Operator Chain,从而减少线程切换、序列化和网络传输开销。
五、时间语义
实时处理中的"时间"并不只有一种。
- Processing Time
Processing Time 使用算子所在机器的系统时间。它延迟低、实现简单,但结果会受到网络延迟、背压、机器负载和故障恢复影响。
- Event Time
Event Time 来自事件自身携带的业务时间,例如订单创建时间。即使记录乱序到达,Flink 仍可按照事件实际发生时间把它放入对应窗口,因此更适合要求结果可重现的业务计算。
less
事件实际发生顺序:
事件 A(10:01) -> 事件 B(10:02) -> 事件 C(10:03)
网络到达顺序:
事件 B(10:02) -> 事件 A(10:01) -> 事件 C(10:03)
Processing Time 窗口:
按 B、A、C 到达系统的时间处理
Event Time 窗口:
按 10:01、10:02、10:03 的业务时间处理
Watermark 是 Flink 对事件时间进度的声明。可以把 Watermark(t) 近似理解为:系统认为事件时间已经推进到 t,原则上不再期待时间戳小于或等于 t 的事件。
Watermark 会触发事件时间窗口和定时器,但它不是"旧数据绝不可能再来"的物理证明。Watermark 推进得慢,结果通常更完整,但窗口输出更晚;推进得快,延迟更低,但更多数据会被判定为迟到。
ini
事件时间轴
10:00 10:01 10:02 10:03 10:04
|-----------|-----------|-----------|-----------|
^
Watermark = 10:02
时间戳 <= 10:02 后续到达的事件
可能被视为迟到事件
在多分区或多输入算子中,事件时间通常受最慢输入的 Watermark 约束。某个长时间没有数据的分区可能阻止整体 Watermark 前进,因此生产环境还需要关注空闲分区检测。
六、状态机制
无状态算子只根据当前输入产生结果,例如字段映射和简单过滤;有状态算子则需要记住此前发生的事情。
典型状态包括:
- 用户累计交易金额;
- 设备最近一次运行状态;
- 窗口中尚未聚合完成的数据;
- 去重所需的历史标识;
- 规则引擎中已经匹配的事件序列。
Flink 原生支持 Keyed State、Operator State、窗口状态和定时器。其中 Keyed State 与 Keyed Stream 使用一致的分区方式,处理某个 Key 的 Subtask 通常可以在本地访问对应状态。
css
输入事件
|
v
keyBy(userId)
|
+------------+-------------+
| | |
v v v
Subtask 0 Subtask 1 Subtask 2
user A/C user B/F user D/E
| | |
本地状态 A/C 本地状态 B/F 本地状态 D/E
State Backend 决定运行中的状态以什么形式存放和访问,以及如何生成状态快照。
- HashMapStateBackend :状态保存在 JVM Heap 中,访问速度快,但规模受内存限制。
- EmbeddedRocksDBStateBackend :状态序列化后保存在本地 RocksDB,可使用本地磁盘并支持增量 Checkpoint。
State Backend 与 Checkpoint Storage 不能混为一谈:
yaml
State Backend
|
| 决定运行状态如何维护、如何生成快照
v
TaskManager 中的运行状态
|
| Checkpoint
v
Checkpoint Storage
|
| 决定快照最终保存在哪里
v
文件系统 / 对象存储 / 其他可靠存储
两者都保存状态,但用途不同。
| 维度 | Checkpoint | Savepoint |
|---|---|---|
| 主要用途 | 非预期故障恢复 | 升级、迁移、扩缩容等计划内运维 |
| 触发方式 | 系统周期性触发 | 用户显式触发 |
| 生命周期 | 通常由 Flink 管理和清理 | 由用户管理和删除 |
| 优化目标 | 创建效率和恢复速度 | 可移植性和运维灵活性 |
| 保留方式 | 可按策略清理 | 通常长期保留 |
七、Checkpoint 容错
对于流式作业,Flink 通过分布式快照和输入重放实现一致恢复。Checkpoint 会记录 Source 的消费位置以及各个有状态算子的状态快照。
rust
Source Operator A Operator B Sink
| | | |
|---- data ---------->------------------->---------------->|
| | | |
|-- Barrier 42 ----->|-- Barrier 42 ---->|-- Barrier 42 ->|
| | | |
| 保存状态 A-42 保存状态 B-42 |
| | | |
+------------ Checkpoint 42 完成 --------------------------+
当任务失败时,Flink 的恢复过程可以概括为:
yaml
任务失败
|
v
找到最近一次成功 Checkpoint
|
+---------------------------+
| |
v v
恢复算子状态 恢复 Source 位置
| |
+-------------+-------------+
|
v
重新读取后续输入
|
v
恢复一致的数据流计算
完整容错能力至少依赖以下条件:
- Checkpoint 已显式启用;
- Source 能保存位置并从指定位置重放;
- Checkpoint Storage 在节点故障后仍然可访问;
- 重启策略和集群高可用配置正确;
- Sink 能处理恢复时可能重新执行的写入。
八、Exactly-once 的边界
Flink 的 Exactly-once 首先表示:在启用 Exactly-once Checkpoint 并成功恢复后,每条输入对 Flink 管理状态 的效果等价于只发生一次。记录在物理执行层面仍可能被重新读取和重新计算。
yaml
端到端 Exactly-once
可重放 Source
|
| 保存读取位置
v
Flink 状态与 Checkpoint
|
| 一致恢复
v
支持事务或幂等写入的 Sink
|
v
目标系统中的 Exactly-once 效果
端到端 Exactly-once 需要整条链路共同配合:
| 环节 | 必要条件 |
|---|---|
| Source | 能记录读取位置,并在恢复时重放 |
| Flink | 启用并成功完成 Exactly-once Checkpoint |
| Storage | Checkpoint 数据持久可靠 |
| Sink | 支持事务提交、两阶段提交、幂等写入或原子提交 |
| 外部系统 | 能正确配合事务、幂等键或提交协议 |
九、资源平台与部署模式
9.1.资源平台
| 平台 | 适用情况 | 主要特点 |
|---|---|---|
| Standalone | 本地验证、固定裸机、已有进程管理平台 | 简单直接,但需要自行补齐进程拉起与资源管理 |
| Native Kubernetes | 云原生、弹性资源、容器化环境 | Flink 可动态申请 TaskManager,适合 Application Mode |
| YARN | 已有 Hadoop 大数据平台 | 可复用队列、权限与资源管理体系 |
| Kubernetes Operator | 希望声明式管理 Flink 作业 | 简化部署、升级、Savepoint 和生命周期管理 |
9.2.部署模式
- Application Mode
每个 Application 拥有独立集群,应用结束后集群退出。其资源隔离和故障隔离较好,推荐生产环境采用这种方式。
css
Application A -> Flink Cluster A
Application B -> Flink Cluster B
Application C -> Flink Cluster C
- Session Mode
预先启动一个长期运行的集群,再向其中提交多个作业。
lua
+------------------+
Job A --------->| |
Job B --------->| Session Cluster |
Job C --------->| |
+------------------+
Session Mode 启动成本低,适合开发测试、交互式 SQL 或大量短作业,但不同作业会共享 JobManager、TaskManager 和故障域,可能出现资源竞争及相互影响。
十、核心特性与优势
10.1.核心特性
- 流批统一
Flink 可以使用统一的数据流模型处理实时无界流和历史有界数据,并允许 DataStream、Table API 和 SQL 在同一运行时上执行。
统一并不意味着简单地把所有批任务当成永不停止的流任务。BATCH 模式可使用阶段式调度、阻塞式 Shuffle、中间结果物化及批专用 Join 和聚合策略。
- 事件时间与乱序处理
Event Time、Watermark、窗口和迟到数据处理共同解决乱序场景。该机制适用于交易、设备、物流和日志等事件发生时间与到达时间不一致的业务。
- 大规模有状态计算
Keyed State、Operator State、定时器及多种 State Backend 使 Flink 能维护长期运行的业务上下文。配合稳定的 Operator UID 和 Savepoint,状态还可随扩缩容重新分配。
- 一致性容错
Flink 使用分布式快照、Checkpoint Barrier、Source 位置和输入重放恢复状态。对齐 Checkpoint 用 Barrier 形成一致切面;非对齐 Checkpoint 可以将部分在途数据纳入快照,缓解严重背压下 Barrier 长时间无法推进的问题。
- 多层 API
| API | 适合场景 |
|---|---|
| SQL | 实时聚合、窗口、Join、持续 ETL |
| Table API | 使用关系模型编写流批查询 |
| DataStream API | 自定义业务处理、状态和数据流转换 |
| ProcessFunction | 精细控制状态、时间、定时器及侧输出 |
- 连接器生态
Flink 提供 Kafka、文件系统、JDBC、Pulsar、Cassandra、Elasticsearch、OpenSearch、Kinesis 和 MongoDB 等连接器。许多连接器采用独立版本发布,且不一定包含在 Flink 二进制发行包中,选型时必须核对连接器与 Flink 主版本的兼容范围。
10.2.Flink 的优势
- 业务时间语义完整
Flink 不只是快速处理记录,还能表达事件时间、乱序、迟到、窗口和定时器。对于依赖真实业务时间的指标,这比单纯按消息到达时间计算更可靠。
- 状态是一等能力
在 Flink 中,状态不是开发者自行维护的外部缓存,而是可以参与 Checkpoint、恢复、扩缩容和重新分配的运行时资源。这降低了大规模状态计算的实现难度。
- 一致性与性能可权衡
Watermark、允许迟到时间、Checkpoint 间隔、State Backend 和对齐策略都允许开发者在完整性、延迟、吞吐、存储和恢复时间之间做工程权衡。
- API 与运行时统一
SQL、Table API 和 DataStream API 共享底层执行引擎,业务可以用 SQL 实现常规聚合,并在复杂规则处切换到 DataStream 或 ProcessFunction。
十一、适用场景
- 实时指标与分析
例如分钟级成交额、在线人数、设备告警数和运营指标。Flink 可以持续聚合事件,并把结果写入 OLAP、搜索引擎或缓存供查询系统使用。
- 实时 ETL 与 CDC
Flink 可持续读取消息或数据库变更日志,完成清洗、维表关联、格式转换和路由,再写入数据湖、数据仓库或下游消息系统。
- 欺诈与异常检测
通过 Keyed State、定时器和复杂事件处理,可以识别短时间频繁交易、异常登录序列、设备状态变化等模式。
- 事件驱动应用
Flink 可以维护用户、订单或设备的长期状态,根据事件持续更新状态并产生新事件,适合复杂业务规则和状态机。
- 实时数仓
Flink SQL 与 CDC 可用于构建持续更新的分层数据模型。不过流式 SQL 的结果可能是 Changelog,而不是只追加的数据流,Sink 必须支持相应的 Append、Upsert 或更新语义。
十二、部署使用
- 本地 Session Cluster
ruby
//下载并解压与本机环境匹配的 Flink 发行包
//启动Session Cluster
./bin/start-cluster.sh
//运行示例程序
./bin/flink run \
./examples/streaming/TopSpeedWindowing.jar
//停止集群
./bin/stop-cluster.sh
- Kubernetes Application Mode
应用 JAR 通常需要打入镜像或放到 JobManager 可访问的远程位置:
bash
FROM flink:<固定版本>
RUN mkdir -p $FLINK_HOME/usrlib
COPY my-flink-job.jar $FLINK_HOME/usrlib/my-flink-job.jar
提交应用:
perl
./bin/flink run \
--target kubernetes-application \
-Dkubernetes.cluster-id=my-flink-app \
-Dkubernetes.container.image.ref=my-flink-image:<固定标签> \
local:///opt/flink/usrlib/my-flink-job.jar
Kubernetes 环境还需要正确配置 ServiceAccount、RBAC、远程 Checkpoint Storage 和日志采集。
- YARN Application Mode
arduino
//配置 Hadoop Classpath
export HADOOP_CLASSPATH="$(hadoop classpath)"
//提交应用
./bin/flink run \
-t yarn-application \
./my-flink-job.jar
生产环境还应核对 YARN 队列资源、ApplicationMaster 重试次数、用户 JAR 的 Classpath 顺序及 Hadoop 版本兼容性。