Flink State Backend 选型、配置、RocksDB 调优、ForSt 与 Changelog 一次讲透

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 / forst
  • execution.checkpointing.dir:checkpoint 存到 HDFS/S3 的哪个目录

理解这个分离,对后面迁移、升级、问题定位非常关键。

开箱即用包含:

  • HashMapStateBackend
  • EmbeddedRocksDBStateBackend
  • ForStStateBackend(实验阶段)

如果你什么都不配,默认用 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(先灰度)

你给的内容里有一条非常关键:

从 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);

使用 state.backend.type

  • hashmap → HashMapStateBackend
  • rocksdb → EmbeddedRocksDBStateBackend
  • forst → 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,影响"同一快照起多作业"的玩法
相关推荐
NCIN EXPE3 小时前
redis 使用
数据库·redis·缓存
MongoDB 数据平台3 小时前
为编码代理引入 MongoDB 代理技能和插件
数据库·mongodb
lUie INGA3 小时前
在2023idea中如何创建SpringBoot
java·spring boot·后端
极客on之路3 小时前
mysql explain type 各个字段解释
数据库·mysql
大鹏说大话3 小时前
SSL证书自动化的未来:ACME协议与Let’s Encrypt实践
网络·安全
代码雕刻家3 小时前
MySQL与SQL Server的基本指令
数据库·mysql·sqlserver
lThE ANDE3 小时前
开启mysql的binlog日志
数据库·mysql
yejqvow123 小时前
CSS如何控制placeholder文字的颜色_使用--placeholder伪元素
jvm·数据库·python
oLLI PILO4 小时前
nacos2.3.0 接入pgsql或其他数据库
数据库
geBR OTTE4 小时前
SpringBoot中整合ONLYOFFICE在线编辑
java·spring boot·后端