2. Flink 核心概念:有状态计算、时间语义、Watermark 与一致性保证
这一章,我们聊聊 Flink 最核心的几个概念。说实话,这些概念是流批一体任务的基石。你如果搞不懂它们,后面运维起来会非常痛苦。我刚开始接触 Flink 时,也在这上面栽过跟头,所以今天我把自己的理解掰开了讲给你听。
2.1 有状态计算:流处理的大脑
什么叫有状态?说白了,就是算子能记住之前处理过的数据。比如你要算过去 5 分钟的用户点击量,总得有个地方存着这 5 分钟的计数吧?这个“计数”就是状态。
核心要点:Flink 的状态是本地化的,存储在 TaskManager 的内存或 RocksDB 中。它不像传统批处理那样每次从数据库读,而是直接访问本地状态,速度极快。
我个人习惯把状态分为两类:
- 托管状态(Managed State):Flink 自动管理,你只需要定义 ValueState、ListState、MapState 等。我建议你优先用这个,省心。
- 原始状态(Raw State):你自己管理字节数组,灵活性高但容易出错。我在项目中几乎不用,除非有极致的性能要求。
我的经验:状态大小要时刻关注。曾经有个任务状态涨到 50GB,导致 Checkpoint 超时。后来我改用 RocksDB 状态后端,并开启了增量 Checkpoint,问题才解决。
2.2 事件时间 vs 处理时间:你到底关心哪个?
这两个概念,我见过太多人搞混了。你想想看:
- 处理时间(Processing Time):数据到达 Flink 算子的机器时间。简单粗暴,但结果不稳定。比如网络抖动导致数据晚到,处理时间窗口就会算错。
- 事件时间(Event Time):数据本身携带的时间戳,比如用户点击网页的时间。这才是业务真正关心的。
| 特性 | 处理时间 | 事件时间 |
|---|---|---|
| 准确性 | 低,依赖机器时钟 | 高,依赖数据自身时间 |
| 延迟 | 最低 | 需要等待迟到数据 |
| 适用场景 | 实时监控、简单聚合 | 金融交易、日志分析 |
| 运维复杂度 | 低 | 高,需要配置 Watermark |
我记得有一次做电商大促的实时大屏,业务方要求统计“用户下单后 10 分钟内的支付率”。如果用处理时间,网络延迟会导致数据错乱。最后我强制要求所有数据携带事件时间,并配置了 Watermark,结果才准确。
2.3 Watermark 机制:给乱序数据一个交代
数据乱序是流处理的常态。比如用户 10:00:00 点击了页面,但这条日志因为网络问题 10:00:05 才到达。事件时间窗口怎么处理?Watermark 就是用来解决这个问题的。
Watermark 本质上是一个时间戳,表示“在这个时间戳之前的数据都已经到达了”。Flink 看到 Watermark(t) 后,就会触发所有时间 <= t 的窗口计算。
避坑指南:我曾经设置 Watermark 生成间隔为 100ms,结果在高峰期导致窗口频繁触发,CPU 飙升。后来调整为 1 秒,并配合 allowedLateness 处理迟到数据,效果好了很多。
Watermark 的生成方式有两种:
- 周期性生成(Periodic):每隔 N 毫秒生成一次。我常用这种方式,适合大多数场景。
- 逐条生成(Punctuated):每条数据都可能触发生成。适合数据量小但需要精确控制的场景。
// 示例:周期性 Watermark 生成
DataStream<MyEvent> stream = env
.addSource(new MySource())
.assignTimestampsAndWatermarks(
WatermarkStrategy.<MyEvent>forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withTimestampAssigner((event, timestamp) -> event.getEventTime())
);
这段代码的意思是:允许数据最多迟到 5 秒。超过 5 秒的,默认丢弃。嗯,这里要注意,如果业务要求不能丢数据,你需要配置 allowedLateness 和侧输出流。
2.4 Checkpoint 与 Savepoint:任务的两道保险
这两个东西,我把它比作数据库的备份和快照。没有它们,流任务就是裸奔。
2.4.1 Checkpoint:自动的救命稻草
Checkpoint 是 Flink 自动触发的,用于故障恢复。它的核心机制是 Barrier——一种特殊的数据标记。Barrier 从 Source 算子开始,随着数据流一起传递,当所有算子都收到 Barrier N 时,就完成了一次 Checkpoint。
关键配置:
checkpoint.interval:触发间隔,我一般设 60 秒。checkpoint.timeout:超时时间,超过这个时间就丢弃这次 Checkpoint。checkpoint.min-pause:两次 Checkpoint 之间的最小间隔,防止频繁触发。
我曾经遇到过一个坑:Checkpoint 一直失败,原因是状态太大,写入 HDFS 超时。后来我把状态后端从 FsStateBackend 换成了 RocksDBStateBackend,并开启了增量 Checkpoint,才稳定下来。
2.4.2 Savepoint:手动的手术刀
Savepoint 和 Checkpoint 原理一样,但它是手动触发的,用于版本升级、代码修改、集群迁移等场景。
| 特性 | Checkpoint | Savepoint |
|---|---|---|
| 触发方式 | 自动 | 手动 |
| 生命周期 | 任务停止后自动删除 | 手动保留 |
| 用途 | 故障恢复 | 版本升级、迁移 |
| 存储格式 | 优化过的二进制 | 标准格式,可跨版本 |
我的习惯:每次上线新代码前,先手动触发一次 Savepoint。如果新版本有问题,可以快速回滚。这个操作我已经形成肌肉记忆了。
2.5 知识体系总览
下面这张图,我把本章的核心概念串起来了。你盯着看 30 秒,应该能理解它们之间的关系。
你看,有状态计算是核心,它依赖时间语义来触发窗口。而 Watermark 是事件时间的“守门员”,保证乱序数据也能正确处理。最后,Checkpoint 和 Savepoint 为这一切提供一致性保证。