1. 先把两个概念分开:State Backend vs Checkpoint Storage
很多人把"状态后端"和"checkpoint 存储"混为一谈,但 Flink 1.13 之后社区更强调它们的分离:
- State Backend:运行时状态如何存(内存对象?本地 RocksDB?远端 SST?)以及如何生成快照
- Checkpoint Storage:checkpoint/savepoint 的数据与元数据写到哪里(JobManager / Filesystem(HDFS/S3...))
你会在配置里同时看到类似:
state.backend.type:选择 hashmap / rocksdb / forstexecution.checkpointing.dir:checkpoint 存到 HDFS/S3 的哪个目录
理解这个分离,对后面迁移、升级、问题定位非常关键。
2. Flink 自带的 State Backend 有哪些
开箱即用包含:
HashMapStateBackendEmbeddedRocksDBStateBackendForStStateBackend(实验阶段)
如果你什么都不配,默认用 HashMapStateBackend。
3. HashMapStateBackend:快,但吃内存,状态在 JVM Heap
3.1 内部表示
- 状态以 Java 对象形式存在 JVM 堆上
- Keyed State / Window 通常是 hash table(触发器、聚合值等都在里面)
3.2 适用场景(官方建议)
- 大状态、长窗口、大 Key/Value State 的作业
- 所有高可用(HA)部署也鼓励使用(注意:HA 指 checkpoint/存储层面要可靠,不代表状态一定不能在堆上)
3.3 关键注意点
- 因为状态就是堆对象,不安全复用对象(object reuse)
- 官方建议把 managed memory 设为 0,让 JVM 堆尽可能留给用户代码和堆状态(在你主要使用堆状态时,这是很重要的)
3.4 常见瓶颈与风险
- 状态总量受 JVM heap 上限严格约束,OOM 风险更直观
- 大状态情况下 GC 压力可能很大(尤其是高 churn 的状态)
一句话:HashMapStateBackend 是"内存换吞吐"的极致路线,快,但状态规模天花板更明显。
4. EmbeddedRocksDBStateBackend:状态上磁盘,规模大但更慢
4.1 内部表示
- 状态保存在 TaskManager 本地目录里的 RocksDB
- 存储的是 序列化后的 byte[] ,比较是按字节比较,而不是 Java 的
hashCode/equals
4.2 重要特性
-
总是异步快照(asynchronous snapshots)
这通常能显著降低 checkpoint 同步阶段对处理的阻塞
-
目前唯一支持增量 checkpoint 的后端(重点)
4.3 限制(非常容易踩坑)
- 由于 RocksDB JNI 基于
byte[]:单个 key 和单个 value 最大 2^31 bytes - 采用 merge 的 state(例如某些 ListState 形态)可能"悄悄累积"超过 2^31,直到下一次读取才爆炸失败
这类问题排查起来很痛,建议对大 value、列表型状态做强约束与监控
4.4 性能与代价
RocksDB 能把状态规模扩到磁盘容量级别,但每次读写都要:
- 序列化/反序列化
- 可能触发磁盘 IO
因此平均性能通常会比堆状态慢一个量级(官方说法也是这个数量级的差距)。不过它换来的是"状态规模几乎只受磁盘限制"。
4.5 对象复用
因为每次都会做(反)序列化,RocksDB backend 可以安全复用对象,这一点在高吞吐场景会更舒服。
一句话:RocksDB 是"用 IO 与 CPU 换状态规模"的路线,状态越大越离不开它。
5. ForStStateBackend:面向云原生的"解耦状态",但仍是实验特性
ForSt 是基于 ForSt 项目的 State Backend(LSM-tree,构建在 RocksDB 之上),主打"分离式状态管理"。
5.1 最关键的区别
- SST 文件可以放在 远程文件系统(HDFS、S3 等)
- TaskManager 本地盘主要用作 缓存
这让状态规模可以突破单机本地盘容量限制
5.2 你要知道的现实
-
目前仍是 experimental,并不完全适合生产"无脑上"
-
总是异步增量快照
-
限制包括:
- 同 RocksDB:单 key/value 最大 2^31 bytes
- 不支持 canonical savepoint、full snapshot、changelog、file-merging checkpoints
只做增量快照 - 远程存储带来的网络延迟会影响 state access,Flink 引入了异步 state access(State API V2)来缓解,但这意味着你要更认真评估应用层配合
一句话:ForSt 更像"云原生/超大状态/远程存储"的未来方向,现在用要做好实验与回滚预案。
6. 如何选 State Backend:一句话策略 + 场景化建议
官方给的核心权衡是:性能 vs 可扩展性。
- HashMapStateBackend:访问快,吞吐高,但状态受内存限制
- EmbeddedRocksDBStateBackend:状态可扩展到磁盘,但读写慢、序列化开销大
- ForStStateBackend:状态可扩展到远程存储,checkpoint/recovery 更轻,但网络延迟与生态限制需要评估
实战建议可以这么落地:
- 状态不大、追求极致吞吐、GC 可控:优先 HashMap
- 状态很大、窗口很长、keyed state 多:优先 RocksDB
- 状态大到本地盘不够、云原生弹性伸缩更重要、你能接受 experimental:考虑 ForSt(先灰度)
7. 关键能力:跨后端切换需要"统一格式 Savepoint"(Flink 1.13+)
你给的内容里有一条非常关键:
从 Flink 1.13 开始,社区统一了 savepoint 的二进制格式
也就是说,你可以用一个 backend 拍 savepoint,再用另一个 backend 恢复(典型:HashMap → RocksDB)
但前提是:
- 你要先升级到 1.13+ 的 Flink
- 用新版本拍一个 savepoint
- 再用新的 backend 从该 savepoint 恢复
这也是为什么 canonical savepoint 在"运维升级/迁移"里价值极高。
8. 配置 State Backend:集群默认 + 作业级覆盖
8.1 作业级(Java)
java
Configuration config = new Configuration();
config.set(StateBackendOptions.STATE_BACKEND, "hashmap"); // 或 "rocksdb" / "forst"
env.configure(config);
8.2 集群默认(flink-conf.yaml)
使用 state.backend.type:
hashmap→ HashMapStateBackendrocksdb→ EmbeddedRocksDBStateBackendforst→ ForStStateBackend- 或者填工厂类全限定名(高级用法)
示例:
yaml
state.backend: hashmap
execution.checkpointing.dir: hdfs://namenode:40010/flink/checkpoints
checkpoint 目录由 execution.checkpointing.dir 决定,所有 backend 都会把 checkpoint 数据与元数据写到这里。
8.3 依赖说明(RocksDB/ForSt)
如果你在 IDE 或代码里直接用类/接口,通常需要添加依赖(provided):
RocksDB:
xml
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-statebackend-rocksdb</artifactId>
<version>2.2.0</version>
<scope>provided</scope>
</dependency>
ForSt:
xml
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-statebackend-forst</artifactId>
<version>2.2.0</version>
<scope>provided</scope>
</dependency>
如果你只是通过配置 state.backend.type 启用,并且作业代码不直接引用 RocksDB/ForSt 的类,通常不需要把依赖打进作业包。
9. RocksDB 的两大杀手锏:增量 checkpoint 与内存托管
9.1 增量 Checkpoint(Incremental Checkpoints)
增量 checkpoint 不是"保存全量备份",而是"只记录上次 checkpoint 之后的变化"。
好处:
- 大状态下 checkpoint 时间可显著下降
- 端到端延迟更稳定(checkpoint 卡顿少)
代价与特性:
-
一个增量 checkpoint 依赖之前的多个 checkpoint
-
Flink 会利用 RocksDB compaction,让增量链条随时间自我收敛,不会无限增长
-
恢复时间不一定更快:取决于瓶颈是网络还是 CPU/IOPs
- 网络瓶颈:可能慢一些(要取更多 delta)
- CPU/IOPs 瓶颈:可能更快(不需要从 canonical 格式重建 RocksDB 表)
启用方式二选一:
配置启用(默认开关):
yaml
execution.checkpointing.incremental: true
代码启用(覆盖配置):
java
EmbeddedRocksDBStateBackend backend = new EmbeddedRocksDBStateBackend(true);
一个很容易误解的点:
启用增量后,Web UI 里的 "Checkpointed Data Size" 可能只显示本次 delta,不代表全量状态大小。
9.2 RocksDB Managed Memory(默认开启)
Flink 会尝试控制 TaskManager 进程的总内存占用,避免在 Yarn/K8s 里被 OOM Killer 杀掉。
默认情况下,Flink 会把 RocksDB 的内存预算绑定到 slot 的 managed memory 上,并用共享 cache + write buffer manager 来控制主要内存消耗项:
- block cache(含 data blocks)
- index & bloom filters
- MemTables(写缓冲)
关键开关:
yaml
state.backend.rocksdb.memory.managed: true
此外还有两个非常实用的比例参数:
- 写路径内存比例(MemTable):
state.backend.rocksdb.memory.write-buffer-ratio(默认 0.5) - 高优先级池比例(给 index/filters):
state.backend.rocksdb.memory.high-prio-pool-ratio(默认 0.1)
强烈不建议设为 0,否则 index/filters 会和 data blocks 抢 cache,性能可能很难看
重要提醒:
只要启用 managed memory,这套机制会覆盖你通过 PredefinedOptions 或 OptionsFactory 对 block cache、write buffer 的部分自定义。
10. Timer 存哪里:RocksDB 还是 Heap
Window/ProcessFunction 大量依赖 timer(事件时间/处理时间)。
在 RocksDB backend 下,timer 默认也进 RocksDB,优点是可扩展、稳健;但 timer 读写也会有成本。
如果你的 timer 数量不大、追求更快触发性能,可以让 timer 上堆:
yaml
state.backend.rocksdb.timer-service.factory: heap
但必须知道两个限制:
- timer 上堆后,timer state 不再支持异步快照(其他 keyed state 仍可异步)
- 如果作业里有写 raw keyed state 的算子(高级自定义算子场景),checkpoint/savepoint 可能失败
结论:除非你明确知道 timer 模型与算子实现细节,否则别轻易把 timer 切到 heap。
11. 专家模式:RocksDB Options 调优的三条路
适合排障或极致性能优化时使用。
1)Predefined Options(预置配置档)
yaml
state.backend.rocksdb.predefined-options: DEFAULT
或代码:
java
EmbeddedRocksDBStateBackend.setPredefinedOptions(
PredefinedOptions.SPINNING_DISK_OPTIMIZED_HIGH_MEM
);
2)关闭 managed memory,用配置直接写 ColumnFamily 选项
这种做法自由度最高,但也最危险:RocksDB native memory 不受 JVM heap 约束,你必须自己保证进程总内存不越界,否则 Yarn/K8s 会直接杀 TaskManager。
3)使用 RocksDBOptionsFactory(最细粒度)
你可以通过 state.backend.rocksdb.options-factory 指定工厂类,像你提供的示例那样注入自定义选项。
适用场景:明确知道要改 compaction、table format、线程数、缓存策略等,并能做压测验证。
12. Changelog State Backend(FLIP-158):降低 checkpoint 长尾
12.1 它解决什么问题
Exactly-once 下,checkpoint 时延主要由三件事决定:
- barrier 传播与对齐(可用 unaligned、buffer debloating 缓解)
- snapshot 同步阶段(异步快照缓解)
- 上传阶段(async phase,增量 checkpoint 能缓解,但 compaction 会导致"重传旧数据")
大规模部署下,"某个 task 在某次 checkpoint 上传巨量数据"这类长尾几乎不可避免。
Changelog 的思路是:
- 持续上传状态变更,形成 changelog
- checkpoint 时只需要上传相关 changelog 片段
- 后台周期性把 backend 物化(materialize),成功后截断 changelog
收益:
- checkpoint 的同步/异步阶段都更短,长尾更小
- 更稳定更低的端到端延迟
- failover 后数据回放更少(通常)
代价: - DFS 上文件更多
- IO 带宽更多
- CPU 序列化更多
- TaskManager 需要更多内存缓冲变更
恢复时间也要评估:changelog 过长需要 replay,会增加恢复耗时;但"恢复耗时 + checkpoint 时延"整体往往仍更优,具体看 state.changelog.periodic-materialize.interval 等参数。
12.2 配置示例
yaml
state.changelog.enabled: true
state.changelog.storage: filesystem
state.changelog.dstl.dfs.base-path: s3://<bucket-name>
execution.checkpointing.max-concurrent-checkpoints: 1
也可按作业开启:
java
env.enableChangelogStateBackend(true);
监控提示:如果 task 被写 changelog 拖慢,会在 UI 里显示 busy(红)。
12.3 升级与回滚
支持两种方向的切换:
- 非 changelog 作业
拍 savepoint 或 checkpoint → 开启 changelog 配置 → 从快照恢复 - changelog 作业
拍 savepoint 或 checkpoint → 关闭 changelog 配置 → 从快照恢复
12.4 当前限制(很重要)
- 最多一个并发 checkpoint
- Flink 1.15 起只有 filesystem 实现可用
- NO_CLAIM 模式不支持(这会影响你从同一快照起多个作业的运维策略)
13. 从旧后端迁移到新 API:对照表
Flink 1.13 后的"后端重构"不改变运行时特性,只是 API 表达更清晰,你可以迁移而不丢状态。
13.1 MemoryStateBackend(旧)
等价于:
- HashMapStateBackend + JobManagerCheckpointStorage
配置:
yaml
state.backend: hashmap
execution.checkpointing.storage: jobmanager
Java:
java
config.set(StateBackendOptions.STATE_BACKEND, "hashmap");
config.set(CheckpointingOptions.CHECKPOINT_STORAGE, "jobmanager");
env.configure(config);
13.2 FsStateBackend(旧)
等价于:
- HashMapStateBackend + FileSystemCheckpointStorage
配置:
yaml
state.backend: hashmap
execution.checkpointing.dir: file:///checkpoint-dir/
execution.checkpointing.storage: filesystem
13.3 RocksDBStateBackend(旧)
等价于:
- EmbeddedRocksDBStateBackend + FileSystemCheckpointStorage
配置:
yaml
state.backend: rocksdb
execution.checkpointing.dir: file:///checkpoint-dir/
execution.checkpointing.storage: filesystem
14. 生产落地清单:选型 + 参数 + 避坑
14.1 选型快速规则
- 状态小且要吞吐:HashMapStateBackend
- 状态大且要稳:EmbeddedRocksDBStateBackend(优先增量 checkpoint)
- 状态超大到本地盘不够、云原生强诉求:评估 ForSt(先实验灰度)
- 想降低 checkpoint 长尾:评估 Changelog(接受额外资源消耗与限制)
14.2 RocksDB 必做项
- 明确 TaskManager 本地目录与磁盘容量(RocksDB 状态在本地盘)
- 大状态建议开启增量 checkpoint:
execution.checkpointing.incremental: true - 先用 managed memory 默认策略跑起来,再做精调
别一上来就关 managed memory 手搓 RocksDB 参数
14.3 高频坑点提醒
- ListState/merge 导致 value 超过 2^31:会"积累到读取才炸"
- 增量 checkpoint UI 显示的 size 不是全量
- 关闭 managed memory 后 native memory 失控,TaskManager 被环境杀死
- timer 切 heap 会引入异步快照限制与 raw keyed state 风险
- Changelog 不支持 NO_CLAIM,影响"同一快照起多作业"的玩法