1. Savepoint 基础概念
各位同学好,我是老张。做 Flink 生产运维这些年,我见过太多因为状态管理不当导致的线上事故。今天咱们先聊聊 Savepoint 最基础的东西——它到底是什么,跟 Checkpoint 有啥区别,以及什么时候该用它。
说白了,Savepoint 就是 Flink 作业的一份「状态快照」。它把算子的所有状态、偏移量、缓冲区数据,完整地拍了一张照片,存到外部存储里。你随时可以用这张照片,把作业恢复到拍照那一刻的状态。
核心定义:Savepoint 是 Flink 作业在某个时间点的完整状态镜像,包含所有算子状态、键控状态、窗口状态、Kafka 偏移量等。它由用户手动触发,用于作业的版本升级、集群迁移、业务回滚等场景。
1.1 Savepoint 与 Checkpoint 的区别
这个问题我面试时经常问。很多人答不上来,或者只说「一个手动一个自动」。嗯,其实没那么简单。
我画了一张对比图,你先看看整体差异:
我整理了一个表格,把关键差异列出来,你保存好,面试或做方案时直接用:
| 对比维度 | Savepoint | Checkpoint |
|---|---|---|
| 触发方式 | 手动触发(CLI / REST API / UI) | 自动触发(基于间隔时间) |
| 设计目的 | 版本升级、集群迁移、业务回滚 | 故障恢复、作业重启 |
| 元数据完整性 | 包含完整算子 ID、状态描述符 | 仅包含运行时必要元数据 |
| 跨版本兼容 | 支持(算子 ID 不变即可) | 不支持(强绑定作业版本) |
| 保留策略 | 用户手动管理,长期保留 | 自动清理,保留最近 N 个 |
| 存储格式 | 标准格式,可移植 | 内部格式,不保证可移植 |
| 触发开销 | 相对较高(全量 + 元数据) | 相对较低(增量/全量可选) |
我的经验:曾经有个同事把 Checkpoint 当 Savepoint 用,升级作业版本后直接恢复,结果状态全乱了。记住——升级、迁移、回滚,必须用 Savepoint。Checkpoint 只负责日常故障恢复。
1.2 Savepoint 的适用场景
那什么时候该用 Savepoint?我总结了四个最常见的场景,都是我在生产环境里实打实踩过的坑:
场景一:作业版本升级
这是最频繁的场景。Flink 版本从 1.13 升到 1.14,或者你改了业务逻辑、调整了算子。直接重启?状态全丢。正确的做法是:先打 Savepoint,再启新作业时指定从 Savepoint 恢复。
命令示例:
# 触发 Savepoint
flink savepoint <jobId> /data/savepoints
# 从 Savepoint 启动新版本作业
flink run -s /data/savepoints/savepoint-xxx \
-c com.example.MyJob \
my-flink-job.jar
场景二:集群迁移
公司要换集群了,从自建 Hadoop 搬到云上,或者从旧集群搬到新集群。作业不能停太久?打 Savepoint,新集群直接拉起。我做过一次跨机房迁移,30 个作业,用 Savepoint 逐个恢复,总停机时间不到 10 分钟。
场景三:业务回滚
上线新功能,结果数据算错了。怎么办?找到上线前打的 Savepoint,回滚到那个时间点。我曾经帮一个金融客户处理过这类问题——他们上线了一个有 Bug 的窗口聚合逻辑,跑了两小时才发现。还好有 Savepoint,回滚后重算,数据一分没丢。
场景四:作业暂停与恢复
有些场景需要暂停作业,比如上游 Kafka 集群维护、数据库迁移。直接停作业?状态全丢。先打 Savepoint,停作业,等维护完再恢复。这个操作我每个月至少做两三次。
注意:Savepoint 不是万能的。如果作业的算子 ID 变了(比如你改了算子名称或添加了新算子),恢复时可能会失败。我建议你在代码里显式设置 uid:
DataStream<String> stream = env
.addSource(kafkaSource)
.uid("kafka-source-id")
.keyBy(...)
.window(...)
.uid("window-id");
养成这个习惯,能省掉你 80% 的恢复问题。
1.3 Savepoint 的存储结构
最后简单说一下 Savepoint 长什么样。你打开 Savepoint 目录,会看到类似这样的结构:
/data/savepoints/
├── savepoint-123456-abcdef/
│ ├── _metadata # 元数据文件(算子 ID、状态描述符)
│ ├── chk-1/ # 状态数据目录
│ │ ├── 0a1b2c3d-... # Task 状态文件
│ │ └── 4e5f6a7b-... # Task 状态文件
│ └── chk-2/
│ └── ...
└── savepoint-789012-ghijkl/
└── ...
元数据文件是关键。它记录了每个算子的 ID、状态类型、序列化器信息。恢复时,Flink 靠它把状态数据映射到新作业的算子上。如果算子 ID 对不上,恢复就会失败。
小技巧:你可以用 flink savepoint --dispose <savepointPath> 手动清理不再需要的 Savepoint。别让它占着磁盘空间,尤其是生产环境,一个 Savepoint 可能几十 GB。