一、为什么要用 State V2?
旧 API 的痛点
- 同步阻塞:
value()/update()
阻塞 task 线程,遇到大状态或远端存储时放大延迟。 - 扩展瓶颈:状态与计算耦合,难以对超大状态做弹性"拉远"。
State V2 带来的核心变化
- 原生异步 :状态访问返回
StateFuture<T>
,链式组合而非阻塞等待。 - 解耦式(分离式)状态管理:状态可安全地"溢写/外置"到远端文件系统,仍保持良好吞吐。
- 按需取数 :
StateIterator<T>
支持惰性迭代,避免"一把梭"把状态全搬回内存。
二、最小可用示例:把同步窗口平均值改成异步
同步版本(旧思路):在 flatMap
中读 ValueState
→ 更新 → 写回。
异步版本(新思路):把逻辑拆成步骤流:
java
public class CountWindowAverage
extends RichFlatMapFunction<Tuple2<Long, Long>, Tuple2<Long, Long>> {
private transient ValueState<Tuple2<Long, Long>> sum; // f0: count, f1: sum
@Override
public void open(OpenContext ctx) {
ValueStateDescriptor<Tuple2<Long, Long>> desc =
new ValueStateDescriptor<>("average",
TypeInformation.of(new TypeHint<Tuple2<Long, Long>>() {}));
sum = getRuntimeContext().getState(desc);
}
@Override
public void flatMap(Tuple2<Long, Long> in, Collector<Tuple2<Long, Long>> out) {
// 1) 异步取值
sum.asyncValue()
// 2) 计算新累计值(thenApply:纯函数计算)
.thenApply(cur -> {
Tuple2<Long, Long> r = (cur == null ? Tuple2.of(0L, 0L) : cur);
r.f0 += 1; r.f1 += in.f1;
return r;
})
// 3) 分支:达到门限发射结果并清理,否则写回
.thenAccept(r -> {
if (r.f0 >= 2) {
out.collect(Tuple2.of(in.f0, r.f1 / r.f0));
sum.asyncClear();
} else {
sum.asyncUpdate(r);
}
});
}
}
// pipeline
env.fromElements(Tuple2.of(1L,3L), Tuple2.of(1L,5L), Tuple2.of(1L,7L), Tuple2.of(1L,4L), Tuple2.of(1L,2L))
.keyBy(v -> v.f0)
.enableAsyncState() // ★ 必须:启用异步状态
.flatMap(new CountWindowAverage())
.print();
关键点:
1)对
KeyedStream
调用enableAsyncState()
;2)用
thenApply/thenAccept/thenCompose/thenCombine
串起步骤;3)不要阻塞 、不要调用类似
get()
的方法(State V2 没有提供)。
三、API 速查:常用异步原语
-
ValueState
- 取值:
StateFuture<T> asyncValue()
- 写值:
asyncUpdate(T)
、清理:asyncClear()
- 取值:
-
ListState
- 追加:
asyncAdd(T)/asyncAddAll(List<T>)
- 读取:
StateFuture<StateIterator<T>> asyncGet()
(惰性按需)
- 追加:
-
ReducingState / AggregatingState<IN, OUT>
- 规约/聚合追加:
asyncAdd(...)
(内部由用户函数合并)
- 规约/聚合追加:
-
MapState<UK,UV>
- 写入:
asyncPut/asyncPutAll
- 读取:
asyncGet(UK)
、遍历:asyncEntries()/asyncKeys()/asyncValues()
- 判空:
asyncIsEmpty()
- 写入:
-
StateIterator
- 判空:
isEmpty()
- 逐个取:
onNext(Consumer<T>) / onNext(Function<T,R>)
- 判空:
-
StateFutureUtils
completedFuture(v)
/completedVoidFuture()
combineAll(futures)
:聚合多个StateFuture
toIterable(futOfIterator)
:谨慎使用,可能丢失惰性优势
四、执行模型与有序性:哪些事"保证"、哪些"不保证"
-
保证的顺序
1)同一 key 的元素进入
flatMap/processElement
的顺序与到达顺序一致;2)同一链条上的
then...
回调按链式声明顺序执行。 -
不保证的顺序
- 不同元素(尤其不同 key)对应的多个
StateFuture
完成先后不可控; - 不同链条之间的回调相对顺序不可控。
- 不同元素(尤其不同 key)对应的多个
-
线程模型
- 所有用户代码(
flatMap
/process
与回调)依旧在 task 线程中执行,不需要担心多线程并发安全; - 但共享可变成员会被"乱序访问",应避免(详见最佳实践)。
- 所有用户代码(
五、最佳实践:写出"异步、可维护"的代码
(一)结构化你的逻辑
- 拆解为 *读取 → 纯函数计算(thenApply) → 分支(thenConditionally) → 写回/继续读取(thenCompose)** 的"步骤流"。
- 通过
StateFuture
在步骤间传递数据,少用共享字段。
(二)避免这些坑
- 不要混用同步与异步状态访问(一次调用里更是禁止)。
- 不要把大集合一次性
toIterable
拉回内存;能用StateIterator#onNext
流式处理就流式处理。 - 不要在回调里修改函数实例的可变字段(如果必须,用每次调用内新建的局部容器 如
AtomicReference
传递)。
(三)状态 TTL
- 仅支持处理时间 TTL;配置通过
StateTtlConfig
加到StateDescriptor
上。 - 过期清理:读时剔除 + 后端后台清理(ForSt 通过 compaction 过滤)。
- 开启 TTL 后,
defaultValue
(本就废弃)不再生效;请自己处理null/过期
的默认值。 - 不要在恢复时把 TTL 从短拉长,可能导致数据语义混乱。
(四)后端选择
- 建议配合 ForSt State Backend 使用(天然支持异步、压缩清理);
- 其他后端虽能用新 API,但状态访问仍为同步,无法发挥异步优势。
六、与 Operator/Broadcast State 的协同
-
Operator State:与并行实例绑定(常见于 Source/Sink)。支持两种重分布:
- 均分(even-split):拼接后等分给各并行度;
- 联合(union) :每并行实例都拿到全量列表(高基数风险:checkpoint 元数据膨胀)。
-
Broadcast State:规则广播的天然形态;map 结构、仅用于"广播流 + 主流"的专用算子中;一个算子可维护多份命名的广播状态。
七、迁移清单:从 State V1 到 V2
步骤一 :在 KeyedStream
上调用 enableAsyncState()
步骤二 :把 StateDescriptor
& 状态句柄替换为 org.apache.flink.api.common.state.v2
包下的类型
步骤三:把同步方法改成异步------例如:
value()
→asyncValue()
(并在then...
中处理);update()
→asyncUpdate()
;clear()
→asyncClear()
;entries()/values()
→asyncEntries()/asyncValues()
+StateIterator
流式处理。
小 Tip:先把读-算-写 切成
thenApply/thenAccept
两步,再按需把"再取一次状态"的逻辑抽成thenCompose
,自然就"异步化"了。
八、性能与稳定性建议
-
端到端调优:
- 与 Watermark/窗口策略配合,避免海量滞留状态;
- 配合
setBufferTimeout()
平衡吞吐与延迟。
-
Backpressure 监控:异步并非银弹,连接外部系统时仍可能受限。
-
可观测性 :为关键
then...
链节打点(耗时、分支比例、异常),定位瓶颈更直观。 -
ForSt compaction:合理设置查询时间间隔与周期性压缩时间,权衡清理速度与 JNI/compaction 开销。
九、总结
- 设计哲学:把"阻塞的状态 I/O"变为"可拼装的步骤流"。
- 工程收益:更低延迟、更高吞吐、更好扩展(大状态友好)。
- 落地路径 :
enableAsyncState()
→ v2 描述符 → 全面改造为StateFuture
链式逻辑 → TTL/后端/清理策略配套到位。
如果你的作业已经在使用 DataStream + Keyed State,强烈建议优先在热点逻辑上尝试"异步化重构"。从一处成功开始,你会很快把全链路搬到 State V2 的节奏里。祝你把实时作业跑得又稳又快!🚀