news 2026/4/23 22:25:44

link 系列第7篇:Flink 状态管理全解析(原理+类型+存储+实操)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
link 系列第7篇:Flink 状态管理全解析(原理+类型+存储+实操)

专栏定位:聚焦 Flink 状态管理核心,从状态概念、分类体系,到 Keyed State、Operator State 详解,再到状态后端底层存储,覆盖原理、实操与生产避坑,兼顾新手入门与开发实战

适用人群:Flink 开发工程师、实时计算落地人员、大数据初学者,需掌握 Flink 基础数据流与窗口操作

核心价值:吃透 Flink 状态管理机制,熟练区分并使用各类状态,合理选择状态后端,解决生产中大状态、扩缩容、状态恢复等核心问题,保障作业稳定运行

一、状态的核心概念(有状态计算的基础)

1.1 什么是状态

状态(State):Flink 流处理中,计算过程中需要记住的中间结果或上下文信息

状态是 Flink 实现复杂有状态计算的核心,它让 Flink 能够在处理无界数据流时,记住历史处理信息,从而实现窗口聚合、复杂事件处理(CEP)、Top-N、数据去重、实时统计等高级功能。

1.2 状态的必要性(无状态 vs 有状态计算)

Flink 计算分为无状态计算和有状态计算,二者的核心区别在于“输出是否依赖历史输入”:

  • 无状态计算:输出仅依赖当前输入数据,与历史处理无关,典型算子如mapfilter(例如:将输入数据转换为大写、过滤掉小于阈值的数据)。

  • 有状态计算:输出依赖当前输入 + 历史输入(即状态),典型算子如sumcountwindowkeyBy + process(例如:统计每个商品的实时销量、计算用户最近30天的行为次数)。

总结:无状态计算仅处理“当下”,有状态计算则能关联“过去与当下”,是实时业务中实现复杂统计的基础。

1.3 状态的分类体系

Flink 中的状态可从“作用域”和“管理方式”两个核心维度进行分类,不同分类的状态适用场景、隔离性差异显著,具体分类如下:

1.3.1 按作用域分类(核心分类)

按作用域分类是最常用的分类方式,核心区分“状态绑定的对象”,分为 Keyed State(键控状态)和 Operator State(算子状态),具体对比如下:

类型作用域隔离性典型场景
Keyed State(键控状态)keyBy()后的每个 key每个 key 独立,互不干扰用户行为分析、实时聚合(如每个用户的最后登录时间、每个商品的销量)
Operator State(算子状态)整个算子实例(并行子任务)每个并行子任务独立Source offset(如 Kafka 消费偏移量)、全局计数器
关键提示:实际业务中,90%+ 的有状态计算场景都使用 Keyed State,因为其“按 key 隔离”的特性,能完美适配按维度统计的需求。

1.3.2 按管理方式分类

按管理方式分类,核心区分“状态由谁负责序列化、分区、恢复”,分为 Managed State(托管状态)和 Raw State(原始状态),具体对比如下:

类型说明推荐度
Managed State(托管状态)由 Flink Runtime 托管,自动完成序列化、分区、状态恢复,无需用户手动处理✅ 强烈推荐(生产首选)
Raw State(原始状态)由用户自行管理,需手动处理序列化、分区、恢复,灵活性高但开发成本高❌ 仅限 Flink 内部组件开发,业务开发不推荐
注意:Keyed State 和 Operator State 均属于 Managed State,也是业务开发中唯一需要关注的状态类型。

二、Keyed State 键控状态(业务核心)

2.1 简介

Keyed State 是 Flink 为keyBy()分区后的数据流设计的状态类型,核心作用是:为每个 key 维护一份独立的状态,实现按 key 维度的精细化有状态计算。

Keyed State 的核心特性:

  • 作用域:仅在keyBy()之后的算子中可用,未经过keyBy()的数据流无法使用 Keyed State;

  • 隔离性:每个 key 的状态完全独立,一个 key 的状态更新、清除不会影响其他 key;

  • 并行性:Flink 会自动将不同 key 的状态分配到不同的 Task 实例,无需用户手动分区;

  • 语义:相当于“每个 key 一个独立的状态机”,各自维护自身的历史上下文。

2.2 Keyed State 的获取方式

Keyed State 不能直接创建,必须在RichFunction(如 RichMapFunction、RichProcessFunction)中,通过RuntimeContext获取,核心代码示例:

// 在 RichFunction 中获取 ValueState(单值状态)ValueState<Long>countState=getRuntimeContext().getState(newValueStateDescriptor&lt;&gt;("count",Long.class));

说明:ValueStateDescriptor用于描述状态的名称(“count”)和数据类型(Long.class),Flink 会根据描述符自动管理状态的序列化和存储。

2.3 Keyed State 的 5 种类型(Managed State)

Flink 提供 5 种内置的 Managed Keyed State,均由 Runtime 托管,自动完成序列化、分区、恢复,覆盖不同业务场景,具体如下:

1. ValueState(单值状态)

  • 语义:存储单个值,类似Map<KEY, T>中的一个 value,是最常用的 Keyed State 类型;

  • 核心 API:

    • value():获取当前 key 的状态值;

    • update(T):更新当前 key 的状态值;

    • clear():清除当前 key 的状态值。

  • 存储方式:直接存储 T 类型的序列化字节;

  • 典型场景:保存每个 key 的最新值、计数器、标志位(如记录每个用户的最后登录时间、每个商品的当前库存)。

2. ListState(列表状态)

  • 语义:存储多个同类型的值,形成一个列表;

  • 核心 API:

    • add(T):向列表中添加一个值;

    • addAll(List<T>):向列表中添加多个值;

    • get():获取列表中的所有值(返回 Iterable);

    • clear():清除当前 key 的列表状态。

  • 注意事项:不保证列表中元素的顺序,避免存储过大的列表(会占用过多内存);

  • 典型场景:缓存每个 key 的最近 N 条记录、窗口数据暂存(如暂存用户最近的5次操作)。

3. MapState<K, V>(Map 状态,高性能首选)

  • 语义:存储 key-value 结构的数据,类似 Java 中的 HashMap,是多维统计的首选;

  • 核心 API:

    • put(K, V):向 Map 中添加/更新键值对;

    • get(K):根据 key 获取对应的 value;

    • contains(K):判断 Map 中是否包含指定 key;

    • entries():获取 Map 中的所有键值对;

    • clear():清除当前 key 的 Map 状态。

  • 核心优势(尤其是 RocksDB 状态后端下):

    • 每个 entry 独立存储,查询、更新单个 key 无需反序列化整个 Map;

    • 内存和 I/O 效率远高于ValueState<Map<K,V>>(避免整体序列化/反序列化)。

  • 典型场景:用户画像(用户 → 行为类型 → 行为次数)、多维指标统计(商品 → 地区 → 销量)。

4. ReducingState(自动聚合状态)

  • 语义:自动对输入数据进行聚合,每次更新都会触发 reduce 函数,只存储聚合结果;

  • 核心 API:add(T):添加输入数据,自动触发 reduce 函数进行聚合;

  • 核心优势:只存储聚合结果,内存占用远小于存储所有原始数据;

  • 典型场景:summaxmin等简单聚合操作(如统计每个商品的累计销量、每个用户的最大消费金额)。

5. AggregatingState<IN, OUT>(带转换的聚合状态)

  • 语义:与 ReducingState 类似,但支持“输入类型 ≠ 输出类型”的聚合,需自定义聚合逻辑;

  • 核心要求:需实现AggregateFunction<IN, ACC, OUT>接口(定义输入、累加器、输出类型);

  • 核心 API:add(IN):添加输入数据,自动触发聚合逻辑;

  • 存储方式:实际存储的是累加器(ACC),而非最终输出结果(OUT);

  • 典型场景:avg(平均值)、variance(方差)等复杂聚合操作(如计算每个用户的平均消费金额)。

2.4 Keyed State 的生命周期

Keyed State 的生命周期分为创建、更新、清除三个阶段,全程由 Flink 托管,用户可手动干预清除操作:

1. 创建

  • 创建时机:在RichFunction.open()方法中,通过RuntimeContext获取状态;

  • 初始化:首次访问状态时,Flink 会自动将其初始化为空(null 或 empty,如 ListState 初始为空列表)。

2. 更新

通过对应状态的 API 完成更新,不同状态的更新方式不同:

  • ValueState:update(T)

  • ListState:add(T)addAll(List<T>)

  • MapState:put(K, V)

  • ReducingState、AggregatingState:add(T)(自动触发聚合)。

3. 清除

清除方式分为手动清除和自动清除,核心注意事项:clear()仅清除当前 key 的状态,无“清除所有 key 状态”的 API(遍历所有 key 不现实)。

  • 手动清除:调用state.clear()方法,主动清除当前 key 的状态;

  • 自动清除:

    • 作业取消:除非保留 Savepoint(快照),否则所有状态会被清除;

    • State TTL(生存时间):推荐方式,设置状态的过期时间,过期后自动清除(避免内存泄漏)。

2.5 Keyed State 的访问模式

Keyed State 的访问严格绑定当前 key,核心操作包括读取、写入、清除,需注意内存释放问题:

  • 读取:state.value()(ValueState)、mapState.get(key)(MapState);

  • 写入:state.update(val)(ValueState)、mapState.put(key, val)(MapState);

  • 清除:state.clear()(清除当前 key 的所有状态);

  • ⚠️ 重要注意:clear()不会立即释放物理内存(尤其使用 RocksDB 状态后端时),需配合 State TTL 机制,确保过期状态及时清理。

2.6 Keyed State 的底层存储

Keyed State 的底层存储方式,高度依赖 State Backend(状态后端)的实现,不同状态后端的存储机制差异巨大。先理解 Keyed State 的逻辑视图,再深入物理存储细节。

2.6.1 逻辑视图:嵌套 Map 结构

从用户角度看,Keyed State 是一个嵌套 Map 结构,逻辑上可表示为:

Map<Key,Map<StateName,StateValue>>
  • 外层 Key:来自keyBy()的分区键(如 user_id、product_id);

  • 内层结构:

    • StateName:状态描述符的名称(如 “last-login-time”、“product-sales”);

    • StateValue:实际状态值,物理内容和结构由 Keyed State 类型(ValueState、MapState 等)决定。

Flink 的核心任务:将这个逻辑嵌套 Map 高效映射到物理存储,确保访问效率和容错性。

2.6.2 物理存储:State Backend 实现

Flink 提供三种核心 State Backend,它们在 Keyed State 的底层存储方式上差异巨大,具体对比如下:

Backend(状态后端)运行时存储位置Checkpoint 存储位置适用场景
MemoryStateBackend(内存状态后端)TaskManager JVM 堆内存(HashMap)JobManager 堆内存本地开发、测试(仅验证逻辑,不适合生产)
FsStateBackend(文件系统状态后端)TaskManager JVM 堆内存(HashMap)分布式文件系统(HDFS/S3)中小状态生产环境(状态大小不超过 TaskManager 堆内存)
RocksDBStateBackend(RocksDB 状态后端)TaskManager 本地磁盘(RocksDB 实例)分布式文件系统(HDFS/S3)✅ 生产大状态(TB 级)、高可靠性需求

2.6.3 RocksDBStateBackend 下的 Keyed State 存储结构(生产重点)

RocksDBStateBackend 是生产环境的首选,其 Keyed State 存储架构如下,核心特点是“磁盘存储 + 高效编码”:

1. 整体架构
TaskManager └── Task (subtask) └── Keyed State Operator └── RocksDB Instance (per subtask) ├── ColumnFamily: "state-namespace-1" ← 每个 StateDescriptor 一个 CF ├── ColumnFamily: "state-namespace-2" └── ...
  • 每个算子子任务(subtask)拥有独立的 RocksDB 实例,避免相互干扰;

  • 每个 StateDescriptor(即每种状态)对应一个 ColumnFamily(CF,列族),实现不同状态的隔离;

  • 所有 key 的状态都存入同一个 RocksDB 实例,但通过“复合 Key 编码”实现隔离。

2. Key 的编码格式

RocksDB 是键值(KV)存储,Flink 需将(key, stateName)编码为单一byte[],作为 RocksDB 的 key,完整编码结构:

[KeyGroupPrefix (2 bytes)] + [KeySerializer] + [NamespaceSerializer]
  • KeyGroupPrefix:键组前缀(2字节),用于状态分片和扩缩容;

  • KeySerializer:key 的序列化结果(来自keyBy()的分区键);

  • NamespaceSerializer:命名空间序列化结果,用于区分不同的状态上下文。

3. Value 的存储
  • 直接存储状态值的序列化结果,不同 Keyed State 类型的存储方式一致(RocksDB 不感知状态类型语义);

  • MapState 的特殊处理(核心优势):

    • 逻辑上是一个 Map,物理上每个 entry 单独存为一条 KV;

    • Key:[KeyGroup][UserKey][MapKey](复合编码);

    • Value:MapValue(Map 中对应的值);

    • 优势:可单独更新/查询某个 Map entry,无需反序列化整个 Map,提升效率。

4. Key Group:状态分片与扩缩容基石

Key Group(键组)是 Flink 状态分片和扩缩容的核心单元,核心特性:

  • 逻辑分片单元,数量 = Max Parallelism(最大并行度);

  • 每个 subtask 负责连续的 Key Group 范围(如 Max Parallelism=128,并行度=4,则每个 subtask 负责32个 Key Group);

  • 扩缩容时,Key Group 是最小迁移单元:

    • 若没有 Key Group,直接按key.hashCode() % parallelism分区,扩容时所有 key 会重新分布,效率极低;

    • 有 Key Group 时,仅迁移需要调整的 Key Group,无需全量重分布,提升扩缩容效率。

三、Operator State 算子状态(辅助核心)

3.1 简介

Operator State(算子状态)是绑定到“算子并行子任务(Subtask)”的状态,与数据流中的具体 key 无关,核心作用是存储算子自身的工作进度或全局元数据。

形象理解:Operator State 是 Flink 连接“流处理”与“外部世界”的桥梁——它让 Flink 能记住自己从哪里读数据(如 Kafka Source 的偏移量),也能实时响应外部指令(如广播规则)。

3.2 与 Keyed State 的核心区别

Operator State 与 Keyed State 的核心差异在于“状态绑定的对象”,具体对比如下:

维度Keyed State(键控状态)Operator State(算子状态)
作用域每个 key每个算子子任务(Subtask)
触发条件必须在keyBy()之后任何算子均可(无需keyBy()
数据关联与数据 key 强绑定与数据内容无关
典型场景用户行为聚合、窗口计算Source offset、全局计数器
选择建议:
  • 如果状态与数据中的某个字段(key)强相关 → 使用 Keyed State;

  • 如果状态描述算子自身的工作进度或全局上下文 → 使用 Operator State。

3.3 Operator State 的 3 种类型(Managed State)

Operator State 同样是 Managed State,Flink 提供 3 种类型,覆盖不同的算子场景,其中 ListState 最常用:

1. ListState(最常用)

  • 语义:存储一个列表,包含多个同类型元素,支持状态重分布(Redistribution);

  • 核心特性:扩缩容时,列表会被拆分或合并,均匀分配给新的子任务;

  • 典型场景:Kafka Source 的分区偏移量(Partition Offsets)、全局错误计数器;

  • 工作原理示例(以 Kafka Source 为例):

    • 假设 Kafka Topic 有 4 个 Partition:[P0, P1, P2, P3]

    • 作业并行度 = 2:

      • Subtask 0 负责[P0, P1]→ ListState 存储[Offset0, Offset1]

      • Subtask 1 负责[P2, P3]→ ListState 存储[Offset2, Offset3]

    • 扩容到并行度=4:

      • Flink 将所有 offset 合并成一个大列表[O0,O1,O2,O3]

      • 通过 Round-Robin(轮询)方式,均匀分配给 4 个新 Subtask(每个子任务负责 1 个 Partition)。

  • ✅ 优势:天然支持动态扩缩容,保证数据不丢不重,是 Operator State 的首选类型。

2. UnionListState(全量广播型)

  • 语义:与 ListState 类似,存储一个列表,但扩缩容行为不同;

  • 核心特性:扩缩容时,会将完整的状态列表全量广播给所有新子任务;

  • 典型场景:需要全局一致视图的初始化数据(较少用),如所有子任务都需要的规则列表、配置信息;

  • 示例:初始化风控规则列表,所有 Task 需要相同的完整规则集,扩容后新 Task 也能获得全部历史规则。

3. BroadcastState<K, V>(特殊且重要)

  • 语义:Map 结构的广播状态,专门用于“广播流 + 主数据流”的关联场景;

  • 关键特性:

    • 只能通过BroadcastStream(广播流)更新,主数据流只能读取,不能修改;

    • 会全量复制到每个算子子任务,确保所有子任务的广播状态一致;

    • 不能通过RuntimeContext获取,只能在BroadcastProcessFunction中访问。

  • 典型场景:动态维表关联、实时规则引擎(如实时更新的风控规则、商品价格表);

  • 场景示例:主数据流是用户行为日志,广播流是实时更新的风控规则,通过 BroadcastState 让所有子任务实时获取最新规则,对用户行为进行风控判断。

3.4 Operator State 的生命周期

Operator State 的生命周期与 Keyed State 不同,其创建、获取、保存都需要通过CheckpointedFunction接口,而非RuntimeContext

1. 创建与获取

  • 必须在RichFunction中(如RichSourceFunctionRichFlatMapFunction);

  • 通过CheckpointedFunction接口获取,核心是FunctionInitializationContext.getOperatorStateStore()

2. 关键接口:CheckpointedFunction

CheckpointedFunction是 Operator State 管理的核心接口,包含两个关键方法:

  • initializeState():作业启动或从 Checkpoint/Savepoint 恢复时调用,用于初始化状态;

  • snapshotState():Checkpoint 时调用,用于保存当前算子子任务的状态。

3. 与 Keyed State 的生命周期区别

  • Keyed State:通过RuntimeContext.getState()获取,生命周期与 key 绑定;

  • Operator State:通过FunctionInitializationContext.getOperatorStateStore()获取,生命周期与算子子任务绑定。

3.5 Operator State 的底层存储

  • 逻辑视图:Map<SubtaskIndex, StateValue>,即每个算子子任务对应一份独立的状态;

  • 物理存储(与状态后端的关系):

    • MemoryStateBackend:运行时存储在 TaskManager JVM 堆内存(HashMap);

    • FsStateBackend:运行时存储在 TaskManager JVM 堆内存,Checkpoint 时写入分布式文件系统;

    • RocksDBStateBackend:运行时仍存储在 TaskManager JVM 堆内存,Checkpoint 时写入分布式文件系统(与 FsStateBackend 行为一致)。

  • ⚠️ 重要注意:无论使用哪种状态后端,Operator State 运行时都存储在 TaskManager 堆内存中;RocksDBStateBackend 仅将 Keyed State 存入磁盘,Operator State 仍在堆内存,需预留足够堆内存。

3.6 扩缩容行为(Rescaling)

Operator State 的扩缩容行为由其类型决定,不同类型的重分布策略不同,具体如下:

State 类型扩容行为缩容行为适用场景
ListState拆分列表(Round-Robin 轮询分配)合并列表(将多个子任务的列表合并)Kafka offset、分片任务
UnionListState全量广播(每个新 Task 得到完整列表)全量广播(所有子任务保留完整列表)全局配置、初始化数据
BroadcastState自动全量复制(新 Task 获得完整广播状态)自动全量复制(剩余 Task 保留完整广播状态)动态规则、维表

3.7 重要注意事项

  • 优先使用 ListState:它是唯一支持高效重分布的 Operator State 类型,适配大多数场景;

  • 避免大对象:Operator State 运行时在堆内存,存储大对象易导致 OOM;

  • ⚠️ 关键:Operator State 不支持 TTL(生存时间),需用户自行管理状态生命周期(如手动清除过期数据)。

四、状态存储与状态后端(底层核心)

4.1 状态存储概述

Flink 状态的存储方式由 State Backend(状态后端)决定:

  • 默认情况:所有状态(Keyed State + Operator State)均存储在 JVM 堆内存中;

  • 问题:当状态数据过多时,易导致 OOM(内存溢出);

  • 解决方案:Flink 提供多种状态后端,将状态存储到磁盘或分布式文件系统,避免 OOM,同时保证容错性。

核心结论:状态后端是 Flink 状态管理的“底层引擎”,决定了状态的存储位置、访问效率和容错能力,生产环境需根据状态大小和可靠性需求选择。

4.2 状态后端的三层模型(整体架构)

Flink 通过“三层模型”将抽象的状态概念落地为高效、可扩展、容错的物理存储系统,核心是“职责分离、层层封装”,架构示意图如下:

三层模型的核心职责:

  • 顶层(StateBackend):总开关,决定 Checkpoint 的持久化策略,负责创建下层具体实现;

  • 中层(OperatorStateBackend):负责管理 Operator State 的具体实现,屏蔽底层存储细节;

  • 底层(KeyedStateBackend):负责管理 Keyed State 的具体实现,是状态存储的核心。

形象理解:StateBackend 是“决策者”,决定状态如何持久化;OperatorStateBackend 和 KeyedStateBackend 是“执行者”,负责具体的存储和访问逻辑。

4.3 顶层:StateBackend —— Checkpoint 策略的总入口

StateBackend 是最上层的抽象接口,不直接处理数据存储,核心作用是“作为工厂和策略中心”,根据配置创建下层的 OperatorStateBackend 和 KeyedStateBackend。

选择哪种 StateBackend,从根本上决定了状态数据的持久化方式和恢复策略,其核心影响的是 Keyed State 的存储方式(Operator State 存储方式相对固定)。

三种状态后端的详细对比(生产重点)

状态后端状态类型运行时(Runtime)存储位置Checkpoint 时存储位置核心特点
MemoryStateBackendKeyed StateTaskManager JVM 堆内存(Heap)JobManager 内存(通过 RPC 发送)轻量、速度快,但易 OOM,JobManager 成为瓶颈,仅适用于测试
Operator StateTaskManager JVM 堆内存(Heap)
FsStateBackendKeyed StateTaskManager JVM 堆内存(Heap)分布式文件系统(HDFS/S3)Checkpoint 持久化,无 JobManager 瓶颈,但状态仍在堆内存,不支持大状态
Operator StateTaskManager JVM 堆内存(Heap)
RocksDBStateBackendKeyed StateTaskManager 本地磁盘(RocksDB 实例)分布式文件系统(支持增量 Checkpoint)支持 TB 级大状态,内存占用低,增量 Checkpoint 高效,生产首选
Operator StateTaskManager JVM 堆内存(Heap)

关键补充说明

  • 运行时 vs Checkpoint:

    • 运行时:作业正常处理数据时,状态实际存放的位置,直接影响内存消耗、GC 压力、访问延迟;

    • Checkpoint 时:做快照(Snapshot)时,状态被持久化到的位置,用于容错恢复(作业失败后可从 Checkpoint 恢复状态)。

  • RocksDBStateBackend 的特殊性(生产重点):

    • 唯一将 Keyed State 从堆内存卸载到磁盘的状态后端,支持 TB 级大状态;

    • Operator State 默认仍在堆内存,因此即使使用 RocksDB,也要为 Operator State 预留足够堆内存;

    • 支持增量 Checkpoint:只上传自上次 Checkpoint 以来变更的 SST 文件(RocksDB 存储格式),极大提升 Checkpoint 效率,减少网络 I/O。

  • MemoryStateBackend 的局限性:

    • 所有状态都在内存中,极易 OOM;

    • Checkpoint 存储在 JobManager 内存,JobManager 成为单点瓶颈;

    • 仅适用于本地开发、测试,或极小状态场景(如简单计数器)。

  • FsStateBackend 的定位:

    • 是 MemoryStateBackend 和 RocksDBStateBackend 之间的折中方案;

    • 状态仍在堆内存,无法解决大状态问题;

    • 适合中小状态 + 需要外部持久化的场景(如状态大小在 GB 级以内)。

4.4 中层:OperatorStateBackend —— 全局状态的管理者

OperatorStateBackend 由DefaultOperatorStateBackend实现,专门负责管理 Operator State,核心作用是“代理层”——根据顶层 StateBackend 的选择,决定 Operator State 的实际存放位置。

核心特点:

  • DefaultOperatorStateBackend是统一接口,屏蔽了底层存储细节,用户无需关心具体存储逻辑;

  • 无论选择哪种顶层 StateBackend,Operator State 的运行时存储位置和数据结构都是一样的——均在 TaskManager 堆内存中;

  • 不同 StateBackend 的差异,仅体现在 Checkpoint 时 Operator State 的持久化位置(如 MemoryStateBackend 存 JobManager 内存,FsStateBackend 存分布式文件系统)。

4.5 底层:KeyedStateBackend —— 键控状态的存储引擎

KeyedStateBackend 是 Flink 状态管理的核心,专门负责管理 Keyed State,不同的状态后端对应不同的 KeyedStateBackend 实现,核心分为两种:

1. HeapKeyedStateBackend

  • 适用的状态后端:MemoryStateBackend、FsStateBackend;

  • 存储位置:TaskManager JVM 堆内存;

  • 数据结构:内部使用 Java 的 HashMap 等集合类,直接存储状态值;

  • 核心特点:

    • 访问速度极快(内存直接访问);

    • 状态大小受限于 TaskManager 堆内存,不支持大状态;

    • 不支持 State TTL,易导致内存泄漏;

    • 适用场景:小规模状态、开发测试。

2. RocksDBKeyedStateBackend

  • 存储位置:本地磁盘(RocksDB);
  • 数据结构:基于 LSM-Tree 的键值存储;
  • 特点:
    • 支持 TB 级状态;
    • 内存占用低(热数据缓存,冷数据在磁盘);
    • 支持增量 Checkpoint 和 TTL;
    • 是生产环境的绝对首选。
  • 适用场景:所有需要大状态或高可靠性的生产作业。
  • 适用的状态后端:RocksDBStateBackend

工作流程示例(以 RocksDBStateBackend 为例)

  1. 用户配置env.setStateBackend(new RocksDBStateBackend("hdfs://..."))
  2. Flink 创建RocksDBStateBackend实例;
  3. 当用户注册ValueState时:
    • RocksDBStateBackend会创建一个RocksDBKeyedStateBackend实例;
    • RocksDBKeyedStateBackend在本地启动一个RocksDB实例;
    • 所有 Keyed State 操作(get, put)都通过 RocksDB 接口完成;
  4. Checkpoint 时:
    • RocksDBKeyedStateBackend触发 RocksDB 的 Snapshot;
    • Snapshot 文件被上传到 HDFS/S3;
    • 文件路径发送给 JobManager
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 13:26:24

基于GEC6818与RFID技术--打造智能公交刷卡终端原型

1. 从零搭建智能公交刷卡终端的技术路线 第一次接触嵌入式开发的朋友可能会觉得公交刷卡终端这种设备很高大上&#xff0c;但其实用GEC6818开发板配合RFID读卡器就能实现核心功能。这个项目最吸引人的地方在于&#xff0c;它完美融合了硬件驱动开发、多线程编程和状态机设计三大…

作者头像 李华
网站建设 2026/4/15 4:26:35

开发板文件传输实战:从PowerShell到adb push的完整工作流

1. 为什么你需要掌握开发板文件传输技能 第一次接触开发板文件传输的新手可能会觉得这个过程很神秘。我记得自己刚开始用开发板时&#xff0c;每次传输文件都要插拔SD卡&#xff0c;不仅效率低下&#xff0c;还经常遇到文件损坏的问题。直到发现了adb这个神器&#xff0c;开发…

作者头像 李华
网站建设 2026/4/16 7:09:55

Thread 类和 Runnable 接口的区别

Thread类 通过继承Thread类创建线程需要重写run方法&#xff0c;每个线程对象拥有独立的资源副本。适合需要独立资源的场景&#xff0c;但受限于Java单继承机制。Runnable接口 实现Runnable接口的类需定义run方法&#xff0c;并将实例作为参数传递给Thread对象。多个线程可共享…

作者头像 李华