Flink从入门到上天系列第十七篇:Flink当中的算子状态

一:算子状态

算子状态:一个算子,会有多个并行子任务。作用范围被限定为当前算子任务。算子状态跟数据的key无关,所以不同key的数据只要被分发到同一个并行子任务,就会访问到同一个Operator State。

算子状态的实际应用场景不如Keyed State多,一般用在Source或Sink等与外部系统连接的算子上,或者完全没有key定义的场景。比如Flink的Kafka连接器中,就用到了算子状态。

当算子的并行度发生变化时 ,算子状态也支持在并行的算子任务实例之间做重组分配。根据状态的类型不同,重组分配的方案也会不同。

算子状态也支持不同的结构类型,主要有三种:ListState、UnionListState和BroadcastState。

二:列表状态

1:使用普通变量实现

复制代码
// 需求,在map算子上,计算每一个并行度处理多少数据
public class Flink08_OpeState_Var {
    public static void main(String[] args) throws Exception {
        // 1. 准备环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        // 2. 配置并行度
        env.setParallelism(2);
        // 3. 读取数据
        DataStreamSource<String> socketDs = env.socketTextStream("bigdata137", 8888);
        // 4. 数据类型转换
        SingleOutputStreamOperator<WaterSensor> wsDs = socketDs.map(new WaterSensorMapFunction());
        SingleOutputStreamOperator<String> map = wsDs.map(new RichMapFunction<WaterSensor, String>() {

            Integer count = 0;

            @Override
            public String map(WaterSensor value) throws Exception {
                // 使用复函数,可以通过复函数获取更丰富的上下文信息。
                return "并行子任务"+getRuntimeContext().getIndexOfThisSubtask() + "处理了"+ ++count+"条数据";
            }
        });

        //7. 打印输出
        map.print();
        //8. 提交作业
        env.execute();
    }
}

2:案例分析

复制代码
1> 并行子任务0处理了1条数据
2> 并行子任务1处理了1条数据
1> 并行子任务0处理了2条数据
2> 并行子任务1处理了2条数据
1> 并行子任务0处理了3条数据
2> 并行子任务1处理了3条数据
1> 并行子任务0处理了4条数据
2> 并行子任务1处理了4条数据


[root@bigdata137 ~]# nc -lk 8888
ws1,1,1
ws1,1,1
ws1,1,1
ws1,1,1
ws1,1,1
ws1,1,1
ws1,1,1
ws1,1,1

基于一个integer就已经实现了需求,并且并行度之间是隔离的。

严格来讲,状态和普通变量就差别一个序列化能力。

如果三个并行度变成了两个并行度。把并行子任务上的状态数据,重新分配给扩缩之后的并行度子任务。

例如:

列表状态:上游三个算子【1,2】【3,4】【5,6】缩容两个算子【135】【246】

联合列表:上游三个算子【1,2】【3,4】【5,6】缩容两个算子【123456】【123456】

3:算子状态实现

把你的普通变量放到状态上去,用状态的能力给你持久化,和做容灾回复。

复制代码
package com.dashu.day08;

import com.dashu.bean.WaterSensor;
import com.dashu.bean.WaterSensorMapFunction;
import org.apache.commons.compress.archivers.dump.DumpArchiveEntry;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.common.state.ListState;
import org.apache.flink.api.common.state.ListStateDescriptor;
import org.apache.flink.api.common.state.OperatorStateStore;
import org.apache.flink.runtime.state.FunctionInitializationContext;
import org.apache.flink.runtime.state.FunctionSnapshotContext;
import org.apache.flink.streaming.api.checkpoint.CheckpointedFunction;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

// 需求,在map算子上,计算每一个并行度处理多少数据
public class Flink09_OpeState_State {
    public static void main(String[] args) throws Exception {
        // 1. 准备环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        // 2. 配置并行度
        env.setParallelism(2);
        env.enableCheckpointing(10000);
        // 3. 读取数据
        DataStreamSource<String> socketDs = env.socketTextStream("bigdata137", 8888);
        // 4. 数据类型转换
        SingleOutputStreamOperator<WaterSensor> wsDs = socketDs.map(new WaterSensorMapFunction());
        SingleOutputStreamOperator<String> map = wsDs.map(new MyMap());

        //7. 打印输出
        map.print();
        //8. 提交作业
        env.execute();
    }
}


class MyMap extends RichMapFunction<WaterSensor,String> implements CheckpointedFunction {

    Integer count = 0;//每个并行度中都有。
    ListState<Integer> countState;//每个并行度中都有

    @Override
    public String map(WaterSensor value) throws Exception {
        return "并行子任务"+getRuntimeContext().getIndexOfThisSubtask() + "处理了"+ ++count+"条数据";
    }

    @Override
    // 开启检查点,检查点执行的时候,会调用这个方法。
    public void snapshotState(FunctionSnapshotContext context) throws Exception {
        System.out.println(" ================= snapshotState ================= ");
        //放之前,先给他清空
        countState.clear();
        //
        countState.add(count);

    }

    @Override
    //初始化状态
    public void initializeState(FunctionInitializationContext context) throws Exception {
        System.out.println(" ================= initializeState ================= ");
        OperatorStateStore operatorStateStore = context.getOperatorStateStore();
        // 获取列表状态
        ListStateDescriptor listStateDescriptor = new ListStateDescriptor("countState", Integer.class);
        countState= operatorStateStore.getListState(listStateDescriptor);


        // 容错机制。数据恢复。
        if(context.isRestored()){
            Integer next = countState.get().iterator().next();
            count = next;
        }
    }
    // ================= initializeState ================= 
    // ================= initializeState ================= 
    // ================= snapshotState ================= 
    // ================= snapshotState ================= 
    //2> 并行子任务1处理了1条数据
    //1> 并行子任务0处理了1条数据
    // ================= snapshotState ================= 
    // ================= snapshotState ================= 
    //2> 并行子任务1处理了2条数据
    // ================= snapshotState ================= 
    // ================= snapshotState ================= 
    // ================= snapshotState ================= 
    // ================= snapshotState ================= 
    // ================= snapshotState ================= 
    // ================= snapshotState ================= 
}

一般什么时候,使用算子状态,从source当中读取数据的时候,或者sink到其他地方的时候的会用到状态。

所以flink中的kafkasource里边维护了状态。也及时kafkasoure的底层,维护了状态,保障了偏移量。

三:联合列表状态

1:两个列表状态的区别

先明确 UnionListState 和你原有 ListState 的核心区别

状态类型 并行度调整时的行为 适用场景
ListState 所有状态项合并后轮询均分给新并行子任务 统计、分片计算(需均分状态)
UnionListState 所有状态项合并后广播给每个新并行子任务 全量数据依赖(每个子任务都需要完整状态)

2:案例分析

复制代码
/**
 * UnionListState 案例:
 * 1. 每个并行子任务记录自己处理过的所有 WaterSensor 的 id
 * 2. 并行度调整/作业重启后,每个新并行子任务能拿到所有历史 id 列表(广播特性)
 */
public class Flink10_OperState_UnionListState {
    public static void main(String[] args) throws Exception {
        // 1. 准备环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        // 2. 配置并行度(可以先设2,测试后调整为3验证Union特性)
        env.setParallelism(2);
        // 开启Checkpoint,保证状态持久化(每10秒一次)
        env.enableCheckpointing(10000);

        // 3. 读取socket数据
        DataStreamSource<String> socketDs = env.socketTextStream("bigdata137", 8888);

        // 4. 转换为WaterSensor类型
        SingleOutputStreamOperator<WaterSensor> wsDs = socketDs.map(new WaterSensorMapFunction());

        // 5. 自定义Map算子,使用UnionListState记录所有id
        SingleOutputStreamOperator<String> resultDs = wsDs.map(new MyUnionMap());

        // 6. 打印输出
        resultDs.print();

        // 7. 提交作业
        env.execute("UnionListState Demo");
    }

    /**
     * 自定义RichMapFunction,实现CheckpointedFunction
     * 核心:使用UnionListState存储所有处理过的WaterSensor id
     */
    static class MyUnionMap extends RichMapFunction<WaterSensor, String> implements CheckpointedFunction {

        // 存储当前并行子任务处理过的所有id(内存临时存储)
        private List<String> currentIdList;
        // UnionListState:算子状态,并行度调整时广播所有状态项
        private UnionListState<String> unionIdState;

        /**
         * 初始化:创建内存列表,初始化UnionListState
         */
        @Override
        public void initializeState(FunctionInitializationContext context) throws Exception {
            System.out.println("===== 并行子任务" + getRuntimeContext().getIndexOfThisSubtask() + " 初始化状态 =====");

            // 1. 初始化内存临时列表
            currentIdList = new ArrayList<>();

            // 2. 获取算子状态存储
            OperatorStateStore operatorStateStore = context.getOperatorStateStore();

            // 3. 定义UnionListState描述符
            ListStateDescriptor<String> stateDesc = new ListStateDescriptor<>(
                    "union_water_sensor_id", // 状态名称
                    String.class             // 状态数据类型
            );

            // 4. 获取UnionListState(关键:getUnionListState 而非 getListState)
            unionIdState = operatorStateStore.getUnionListState(stateDesc);

            // 5. 容错恢复:作业重启时,从UnionListState中读取所有id到内存列表
            if (context.isRestored()) {
                System.out.println("===== 并行子任务" + getRuntimeContext().getIndexOfThisSubtask() + " 恢复状态 =====");
                // 遍历UnionListState,将所有历史id加入内存列表
                Iterator<String> stateIterator = unionIdState.get().iterator();
                while (stateIterator.hasNext()) {
                    currentIdList.add(stateIterator.next());
                }
            }
        }

        /**
         * 核心处理逻辑:每条数据加入内存列表,返回当前子任务的id列表信息
         */
        @Override
        public String map(WaterSensor value) throws Exception {
            // 1. 获取当前WaterSensor的id
            String sensorId = value.getId();

            // 2. 将id加入内存临时列表
            currentIdList.add(sensorId);

            // 3. 构造返回结果:当前子任务ID + 已处理的id数量 + 所有id列表
            return "并行子任务" + getRuntimeContext().getIndexOfThisSubtask()
                    + " | 累计处理id数:" + currentIdList.size()
                    + " | 所有id:" + currentIdList;
        }

        /**
         * Checkpoint触发时:将内存列表中的所有id写入UnionListState持久化
         */
        @Override
        public void snapshotState(FunctionSnapshotContext context) throws Exception {
            System.out.println("===== 并行子任务" + getRuntimeContext().getIndexOfThisSubtask() + " 持久化状态 =====");

            // 1. 清空原有状态(避免重复存储)
            unionIdState.clear();

            // 2. 将当前内存列表中的所有id写入UnionListState
            unionIdState.addAll(currentIdList);
        }
    }
}

3:总结

复制代码
托管状态:
  描述: 由Flink框架管理状态的存储、序列化以及容错恢复等
  算子状态:
    作用范围: 算子的每一个并行子任务上(分区、并行度、slot)
    类型:
      - ListState
      - UnionListState
      - BroadcastState(重点)
    使用步骤:
      - 类必须实现CheckpointedFunction接口
      - 接口方法:
          initializeState: 初始化状态
          snapshotState: 对状态进行备份
    特点:
      - 声明位置: 与普通成员变量声明位置相同
      - 作用范围: 与普通成员变量作用范围相同
      - 持久化: 可被持久化
      - 底层原理: snapshotState阶段将普通变量放入状态中持久化,相当于普通变量也被持久保存
  键控状态:
    作用范围: 经过keyBy之后的每一个组,组和组之间状态是隔离的
    类型:
      - ValueState
      - ListState
      - MapState
      - ReducingState
      - AggregatingState
    使用步骤:
      - 声明位置: 在处理函数类成员变量位置声明状态
      - 注意: 虽然在成员变量位置声明,但是作用范围是keyBy后的每一个组
      - 初始化: 在open方法中对状态进行初始化
      - 使用: 在具体的处理函数中使用状态

四:广播状态(重点)

广播状态也是算子状态的一种,作用范围是算子子任务。他的使用方式是固定的。

我们需要处理一条流的数据,需要用到另外一条流的规则。这个时候需要用到广播状态,但是如果两个流,数据两都特别大,这个时候,就不适合使用这个状态了。

相关推荐
电商API&Tina2 小时前
1688跨境寻源通API数据采集: 获得1688商品详情关键字搜索商品按图搜索1688商品
大数据·前端·数据库·人工智能·爬虫·json·图搜索算法
武子康2 小时前
大数据-249 离线数仓 - 电商分析 Hive 数仓实战:订单拉链表到 DWS 宽表设计与加载脚本详解
大数据·后端·apache hive
Lalolander2 小时前
生产计划频繁被打乱?利用MES构建动态排程与订单影响分析体系
大数据·人工智能·mes·制造执行系统·工单管理软件·工厂管理软件·生产计划管理
忆~遂愿2 小时前
摆脱浏览器书签混乱!Fenrus+cpolar解锁公网访问新玩法
大数据
IvanCodes2 小时前
三、Kafka安装详细教程
大数据·分布式·kafka
雷焰财经2 小时前
破解差异化转型之困:从宇信科技“双龙头”项目看其全栈赋能之道
大数据·人工智能·科技
ajassi20003 小时前
Gitlab和Github的用途和指令
大数据·elasticsearch·搜索引擎
shuangrenlong3 小时前
androidstudio的changelist显示问题
大数据·elasticsearch·搜索引擎
geneculture3 小时前
协同智能视域AI大模型的文明跃迁价值:普惠HI跃升的契机
大数据·人工智能·机器学习·数据挖掘·融智学的重要应用·哲学与科学统一性·融智时代(杂志)