2. 状态后端类型详解:HashMapStateBackend、EmbeddedRocksDBStateBackend、MemoryStateBackend(已废弃)

状态后端,说白了就是 Flink 存状态的地方。你想想看,一个流任务跑起来,中间会产生各种累加器、窗口缓存、聚合结果——这些东西放哪?怎么放?放多久?这就是状态后端要解决的问题。

我个人习惯把状态后端比作「仓库」。有的仓库是内存,快但装不下太多;有的仓库是磁盘,能装但慢一点;还有的仓库已经被淘汰了,千万别再用。今天我们就把这三种仓库掰开揉碎讲清楚。

核心结论先放这:生产环境选 RocksDB,调试或小任务用 HashMap,MemoryStateBackend 已经废弃,别碰。

Flink 状态后端类型总览 HashMapStateBackend 内存存储 · 快 · 容量有限 RocksDBStateBackend 磁盘存储 · 大 · 生产首选 MemoryStateBackend 已废弃 · 别用 特性对比:存储介质 · 容量上限 · 性能 · 适用场景 · 容错机制 特性 HashMap RocksDB Memory(废弃) 存储介质 JVM 堆内存 本地磁盘 + 内存缓存 JVM 堆内存 状态容量 受限于 JVM 堆大小 TB 级别 受限于 JVM 堆大小 性能 极快(纯内存) 中等(序列化开销) 极快(纯内存) 推荐场景 小状态 · 调试 · 本地 大状态 · 生产 · 高可用 无(已废弃)

2.1 HashMapStateBackend:快,但别贪心

HashMapStateBackend 把状态全部存在 JVM 堆内存里。每次访问状态,就是直接读写 Java 对象的引用,没有序列化、没有磁盘 IO——所以它快得离谱。

我在项目中遇到过一个小任务,状态量不到 100MB,用 HashMap 跑,延迟稳定在 5ms 以内。换成 RocksDB 后,同样的逻辑延迟飙到了 30ms。这就是 HashMap 的优势:零序列化开销。

什么时候用 HashMap?

  • 状态总量 < 1GB(保守点,500MB 以内更安全)
  • 单次 checkpoint 时间要求极短
  • 本地开发调试,图省事
  • 任务并行度不高,不会触发频繁 GC

但你要小心——HashMap 的容量上限就是 JVM 堆大小。一旦状态膨胀,轻则 Full GC 卡死,重则 OOM 直接挂掉。我见过有人把 10GB 的状态塞进 HashMap,结果 TaskManager 每 30 秒一次 Full GC,任务根本跑不动。

避坑指南:我曾经接手过一个线上任务,状态后端配了 HashMap,状态量 2GB 左右。运行一周后,GC 时间占比超过 40%,吞吐量掉了一半。后来切到 RocksDB,问题立刻解决。记住:HashMap 不是不能用于生产,而是你必须清楚状态的量级。

2.2 EmbeddedRocksDBStateBackend:生产环境的扛把子

RocksDB 是一个嵌入式的 KV 存储引擎,Flink 把它包装成状态后端。状态数据先写到内存中的 MemTable,满了再刷到磁盘上的 SST 文件。读写时还有 block cache 做加速——说白了就是「内存 + 磁盘」的混合方案。

我个人习惯把 RocksDB 当作默认选项。不管状态是 100MB 还是 100GB,它都能扛住。为什么?因为 RocksDB 支持增量 checkpoint,每次只上传变更过的 SST 文件,而不是全量快照。这在状态大的时候,节省的时间不是一星半点。

RocksDB 核心优势:

  • 状态容量可达 TB 级别,不受 JVM 堆限制
  • 增量 checkpoint,大幅减少快照时间
  • 支持异步状态访问,不阻塞主处理线程
  • 内置压缩算法(LZ4、Snappy、ZSTD),节省磁盘空间

不过 RocksDB 也有代价。每次读写状态都要经过序列化/反序列化,性能比 HashMap 慢一个数量级。另外,RocksDB 的调优参数很多——比如 block cache 大小、write buffer 数量、并行 compaction 线程数——调不好反而会出问题。

我的调优经验:刚开始用 RocksDB 时,我习惯把 block cache 设成 256MB,write buffer 设成 128MB。后来发现对于大状态任务,block cache 可以放大到 512MB 甚至 1GB,能显著提升热数据的访问速度。但别贪心——cache 太大反而会挤占 JVM 堆内存,引发 GC 问题。

2.3 MemoryStateBackend:历史的尘埃

MemoryStateBackend 在 Flink 1.13 版本被标记为废弃,到 1.15 版本正式移除。它和 HashMap 一样把状态存在 JVM 堆里,但区别在于:checkpoint 也存到 JobManager 的堆内存中。

这意味着什么?意味着你的状态一旦超过几百 MB,JobManager 直接 OOM。而且 JobManager 挂了,所有 checkpoint 数据全部丢失——连恢复的机会都没有。

千万别用 MemoryStateBackend!如果你还在用 Flink 1.12 或更早版本,赶紧迁移到 HashMapStateBackend 或 RocksDBStateBackend。我曾经在旧项目里看到有人用 MemoryStateBackend 跑生产,状态 2GB,JobManager 堆才 4GB——结果每周挂一次,每次恢复都要重跑半天数据。

2.4 三种后端的核心差异对比

对比维度 HashMapStateBackend EmbeddedRocksDBStateBackend MemoryStateBackend(废弃)
状态存储位置 TaskManager JVM 堆 本地磁盘 + 内存缓存 TaskManager JVM 堆
Checkpoint 存储 外部文件系统(HDFS/S3) 外部文件系统(HDFS/S3) JobManager 堆内存
状态容量上限 受限于 TaskManager 堆大小 TB 级别(受限于本地磁盘) 受限于 TaskManager 堆大小
读写性能 极快(无序列化) 中等(有序列化开销) 极快(无序列化)
增量 Checkpoint 不支持 支持 不支持
适用场景 小状态、调试、本地开发 大状态、生产环境、高可用 无(已废弃)

2.5 如何选择?我的决策逻辑

每次选状态后端,我都会问自己三个问题:

  1. 状态有多大?——小于 500MB 且不会增长?考虑 HashMap。大于 500MB 或不确定?直接 RocksDB。
  2. 对延迟敏感吗?——需要毫秒级响应?HashMap 更合适。可以接受几十毫秒?RocksDB 没问题。
  3. 这是生产环境吗?——是,无脑 RocksDB。不是,HashMap 省事。

一句话总结:HashMap 是跑车,快但装不了多少东西;RocksDB 是卡车,能装但慢一点。生产环境你需要的是一辆可靠的卡车,而不是一辆随时可能抛锚的跑车。