13、Flink 的 Operator State 详解

1.算子状态 (Operator State)

算子状态 (或者非 keyed 状态)是绑定到一个并行算子实例的状态,Kafka consumer 每个并行实例维护了 topic partitions 和偏移量的 map 作为它的算子状态。

当并行度改变的时候,算子状态支持将状态重新分发给各并行算子实例,处理重分发过程有多种不同的方案。

算子状态作为一种特殊类型的状态使用,用于实现 source/sink,以及无法对 state 进行分区而没有主键的这类场景中。

注意: Python DataStream API 仍无法支持算子状态。

2.使用 Operator State

用户可以通过实现 CheckpointedFunction 接口来使用 operator state。

a)CheckpointedFunction 概述

CheckpointedFunction 接口提供了访问 non-keyed state 的方法,需要实现如下两个方法:

复制代码
void snapshotState(FunctionSnapshotContext context) throws Exception;

void initializeState(FunctionInitializationContext context) throws Exception;

进行 checkpoint 时会调用 snapshotState(),自定义函数初始化时会调用 initializeState(),初始化包括第一次自定义函数初始化和从之前的 checkpoint 恢复;因此 initializeState() 不仅是定义不同状态类型初始化的地方,也需要包括状态恢复的逻辑。

当前 operator state 以 list 的形式存在;这些状态是一个 可序列化 对象的集合 List,彼此独立,方便在改变并发后进行状态的重新分派,根据状态的不同访问方式,有如下几种重新分配的模式

  • Even-split redistribution: 每个算子都保存一个列表形式的状态集合,整个状态由所有的列表拼接而成;当作业恢复或重新分配的时候,整个状态会按照算子的并发度进行均匀分配。;比如说,算子 A 的并发度为 1,包含两个元素 element1element2,当并发度增加为 2 时,element1 会被分到并发 0 上,element2 则会被分到并发 1 上。
  • Union redistribution: 每个算子保存一个列表形式的状态集合;整个状态由所有的列表拼接而成;当作业恢复或重新分配时,每个算子都将获得所有的状态数据【不建议使用】
b)带缓冲的 SinkFunction

案例SinkFunctionCheckpointedFunction 中进行数据缓存,然后统一发送到下游,演示了列表状态数据的 event-split redistribution。

复制代码
public class BufferingSink
        implements SinkFunction<Tuple2<String, Integer>>,
                   CheckpointedFunction {

    private final int threshold;

    private transient ListState<Tuple2<String, Integer>> checkpointedState;

    private List<Tuple2<String, Integer>> bufferedElements;

    public BufferingSink(int threshold) {
        this.threshold = threshold;
        this.bufferedElements = new ArrayList<>();
    }

    @Override
    public void invoke(Tuple2<String, Integer> value, Context contex) throws Exception {
        bufferedElements.add(value);
        if (bufferedElements.size() >= threshold) {
            for (Tuple2<String, Integer> element: bufferedElements) {
                // send it to the sink
            }
            bufferedElements.clear();
        }
    }

    @Override
    public void snapshotState(FunctionSnapshotContext context) throws Exception {
        checkpointedState.update(bufferedElements);
    }

    @Override
    public void initializeState(FunctionInitializationContext context) throws Exception {
        ListStateDescriptor<Tuple2<String, Integer>> descriptor =
            new ListStateDescriptor<>(
                "buffered-elements",
                TypeInformation.of(new TypeHint<Tuple2<String, Integer>>() {}));

        checkpointedState = context.getOperatorStateStore().getListState(descriptor);

        if (context.isRestored()) {
            for (Tuple2<String, Integer> element : checkpointedState.get()) {
                bufferedElements.add(element);
            }
        }
    }
}

initializeState 方法接收一个 FunctionInitializationContext 参数,用来初始化 non-keyed state 的 "容器" 即 ListState 用于在 checkpoint 时保存 non-keyed state 对象,和 keyed state 类似,StateDescriptor 会包括状态名字、以及状态类型相关信息。

复制代码
ListStateDescriptor<Tuple2<String, Integer>> descriptor =
    new ListStateDescriptor<>(
        "buffered-elements",
        TypeInformation.of(new TypeHint<Tuple2<String, Integer>>() {}));

checkpointedState = context.getOperatorStateStore().getListState(descriptor);

调用不同的获取状态对象的接口,会使用不同的状态分配算法,比如 getUnionListState(descriptor) 会使用 union redistribution 算法, 而 getListState(descriptor) 则使用 even-split redistribution 算法。

当初始化状态对象后,通过 isRestored() 方法判断是否从之前的故障中恢复,如果该方法返回 true 则表示从故障中进行恢复,会执行接下来的恢复逻辑,BufferingSink 中初始化时,恢复回来的 ListState 的所有元素会添加到一个局部变量中,供下次 snapshotState() 时使用,然后清空 ListState,再把当前局部变量中的所有元素写入到 checkpoint 中。

同样可以在 initializeState() 方法中使用 FunctionInitializationContext 初始化 keyed state。

c)带状态的 Source Function

需要保证更新状态以及输出的原子性(用于支持 exactly-once 语义),需要在发送数据前获取数据源的全局锁。

复制代码
public static class CounterSource
        extends RichParallelSourceFunction<Long>
        implements CheckpointedFunction {

    /**  current offset for exactly once semantics */
    private Long offset = 0L;

    /** flag for job cancellation */
    private volatile boolean isRunning = true;
    
    /** 存储 state 的变量. */
    private ListState<Long> state;
     
    @Override
    public void run(SourceContext<Long> ctx) {
        final Object lock = ctx.getCheckpointLock();

        while (isRunning) {
            // output and state update are atomic
            synchronized (lock) {
                ctx.collect(offset);
                offset += 1;
            }
        }
    }

    @Override
    public void cancel() {
        isRunning = false;
    }

    @Override
    public void initializeState(FunctionInitializationContext context) throws Exception {
        state = context.getOperatorStateStore().getListState(new ListStateDescriptor<>(
            "state",
            LongSerializer.INSTANCE));
            
        // 从已保存的状态中恢复 offset 到内存中,在进行任务恢复的时候也会调用此初始化状态的方法
        for (Long l : state.get()) {
            offset = l;
        }
    }

    @Override
    public void snapshotState(FunctionSnapshotContext context) throws Exception {
        state.update(Collections.singletonList(offset));
    }
}

要获取 checkpoint 成功消息的算子,可以参考 org.apache.flink.api.common.state.CheckpointListener 接口

【当算子完成 checkpoint 后会回调 notifyCheckpointComplete() 方法】。

相关推荐
HZZD_HZZD1 小时前
智能水电表厂家怎么评估:制造能力、资质合规与交付保障的核查清单
大数据·制造
Raas1001 小时前
AI网关和LiteLLM区别在哪?MAI Gateway(魔芋企业级AI网关)统一治理方案深度解析
大数据·人工智能·gateway·mai gateway·企业级产品
GlobalInfo1 小时前
2026新能源汽车线束研究报告:市场规模与十五五风向
大数据·人工智能·新能源
林墨聊AIGC2 小时前
AI视频怎么做跳舞的动作效果:从入门到精通的舞蹈动画制作指南
大数据·人工智能·自动化·aigc·音视频
blues92572 小时前
赋能企业AI高效落地|Vantage万极FDE四天实战营杭州首班开启报名
大数据·人工智能·fde
洋洋不叫杨杨2 小时前
外贸独立站SEO优化全攻略:从0到1的完整指南
大数据·搜索引擎·seo优化·谷歌seo·google seo·独立站seo·外贸seo
RAOY的AI笔记3 小时前
GPT-6 Astra开发指南:API调用、工具使用与AI Agent应用思路
大数据·人工智能·gpt
麻辣布丁3 小时前
Git冲突原因与解决方法全解
大数据·git·elasticsearch
科技每日热闻3 小时前
中国企业出海开展业务,如何挑选可安全合规使用国际大模型的云平台?Amazon Bedrock 在同一平台完成国际模型接入、区域选择与合规治理
大数据·人工智能·安全·ai
SelectDB3 小时前
Apache Doris+ Paimon 2.0:构建 Agentic AI 数据闭环
大数据·数据库·数据分析