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() 方法】。

相关推荐
科技发布19 分钟前
可出具正规收录回执广告平台推荐,朝闻通合规投放满足企业审计需求
大数据·人工智能·科技·媒体
智慧景区与市集主理人26 分钟前
巨有科技:市集笔记内容分发,低成本打造本地流量曝光矩阵
大数据·笔记·科技
Promise微笑30 分钟前
激光清障仪市场与技术深度分析:基于原理、应用及厂家格局的综述
大数据·人工智能·物联网
财迅通Ai1 小时前
南华期货半年净利预期高增,稀缺自主清算壁垒驱动跨境业务规模放量
大数据·人工智能·区块链·南华期货
风途科技~2 小时前
手持式雷达测速仪:不限道路厂区,可设置限速阈值实时监测车辆速度
大数据·人工智能
GEO_ai_zhijian2 小时前
饮料生产线质量检测系统哪家好
大数据·人工智能·python
wuhanzhanhui2 小时前
从光影切割到金属艺术:2026武汉激光焊接切割及钣金加工展会,重塑制造精度
大数据·人工智能
杨逢昌工厂6S管理2 小时前
28- 杨逢昌:制造企业仓储6S反复混乱解决方案——四维定置标准化管控体系,让物料混放、通道堵塞等重复性乱象减少90%
大数据·经验分享·笔记·职场和发展·制造·学习方法
阳明山水3 小时前
从论文到生产:销量预测系统的工程化落地实践
大数据·人工智能·深度学习·机器学习·架构
多年小白3 小时前
长征十号乙「网系回收」成功 × 商业航天板块 · 腾讯自选股圆桌报告
大数据·人工智能