首页/数据/数仓建模速查/Flink 状态与状态后端
湖仓与引擎63 条

Flink 状态与状态后端

状态可分成键控状态与算子状态:键控状态随 key 分布、并行度高;状态后端常用 HashMapStateBackend(内存)与 EmbeddedRocksDBStateBackend(磁盘)。

数仓建模速查数据更新于 2026-10-13

Flink 状态分哪几类

状态是流计算的记忆:跨事件累计、去重、会话切分都靠它。键控状态按 key 分区存放,同一个 key 一定落到同一个并行子任务,因此可以放心做累计;算子状态挂在算子实例上,与 key 无关。状态放在哪里由状态后端决定,内存快但容量小,磁盘慢但能扛住大状态。

基本信息

键控状态ValueState / ListState / MapState / ReducingState
算子状态算子实例级共享,与 key 无关
状态后端HashMapStateBackend(内存)/ EmbeddedRocksDBStateBackend(磁盘)
过期清理StateTtlConfig 配置 TTL
影响面并行度调整会改变 key 的分组归属

典型使用场景

  • 实时累计:当天各门店的支付金额按 key 累加
  • 去重:用状态记录已处理的订单号,避免重复计数
  • 会话切分:同一用户超过一定时间没有事件则切分新会话
  • 维表缓存:把不常变的映射放进键控状态,减少外部查询
  • 告警抑制:短时间内重复告警只在首次触发

状态与 TTL 示例

状态与 TTL 示例
ValueStateDescriptor<Long> desc =
    new ValueStateDescriptor<>("pay_sum", Types.LONG);

// 给状态设置过期时间,避免无界增长
StateTtlConfig ttl = StateTtlConfig
    .newBuilder(Time.days(7))
    .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
    .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
    .build();
desc.enableTimeToLive(ttl);   // 配置完成后注册到 RuntimeContext

状态后端对照

后端状态存放适用判断
HashMapStateBackendJVM 堆内存状态小、追求低延迟
EmbeddedRocksDBStateBackend本地磁盘 + 内存缓存状态大、键数量多
堆内存 + 大状态不推荐易触发内存溢出
磁盘后端 + 高频读写需评估读写放大影响吞吐

状态为什么会失控

状态体积增长通常不是数据量增长造成的,而是没有过期策略:用户维度、会话维度的 key 只增不减,每条记录都往状态里写一笔,几个月后单个子任务的内存占用就能顶上早期整个作业的量。检查点的快照体积同步增长,做一次检查点的时间越来越长,最后超过超时阈值频繁失败。

选择状态后端不能只看内存:RocksDB 后端能容纳远超堆内存的状态,代价是每次读写都要序列化,对延迟敏感的场景要实测。已知状态上限很小的情况,内存后端反而更稳。
没有 TTL 的大状态会让检查点耗时持续上升直至超时,作业反复重启并重新加载状态,形成恶性循环。上线前就把 TTL 定下来,比事后补救容易得多。

易错点

  • 状态不设过期策略,检查点体积与耗时逐月上升,最终超时失败
  • 用算子状态做按 key 的累计,并行度变化后累计结果错乱
  • 大状态配内存后端,作业运行一段时间后频繁内存溢出
  • 把维表全量塞进状态且不设上限,恢复时加载时间超出业务可接受范围

数据来源与参考

  • Apache Flink 官方文档:Stateful Stream Processing
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌