Flink学习10-状态管理

概述

众所周知,flink的一个特性是有状态计算,像经常使用的聚合算子sum就是一个有状态计算。flink是来一条数据处理一条数据,如果没有状态管理中间结果,那么每次只会给我们返回当前这条数据结果而不是累加的,这显然不符合我们预期。

状态分类

按照官方给到分类可以划分为:
Keyed State:在KeyedStream中管理状态
Operator State:绑定在一个操作算子中的状态,它主要是一种特殊类型的状态,用于源/接收器实现和场景中,例如在kafka给每个并行度单独管理状态数据。
Broadcast State:Operator State的一种,将需要下发/广播配置、规则等低吞吐事件流到下游所有 task。

而Keyed State的状态又可以细化分为:
ValueState: 保存一个可以更新和检索的值(如上所述,每个值都对应到当前的输入数据的 key,因此算子接收到的每个 key 都可能对应一个值)。 这个值可以通过 update(T) 进行更新,通过 T value() 进行检索。

ListState: 保存一个元素的列表。可以往这个列表中追加数据,并在当前的列表上进行检索。可以通过 add(T) 或者 addAll(List) 进行添加元素,通过 Iterable get() 获得整个列表。还可以通过 update(List) 覆盖当前的列表。

ReducingState: 保存一个单值,表示添加到状态的所有值的聚合。接口与 ListState 类似,但使用 add(T) 增加元素,会使用提供的 ReduceFunction 进行聚合。

AggregatingState<IN, OUT>: 保留一个单值,表示添加到状态的所有值的聚合。和 ReducingState 相反的是, 聚合类型可能与 添加到状态的元素的类型不同。 接口与 ListState 类似,但使用 add(IN) 添加的元素会用指定的 AggregateFunction 进行聚合。

MapState<UK, UV>: 维护了一个映射列表。 你可以添加键值对到状态中,也可以获得反映当前所有映射的迭代器。使用 put(UK,UV) 或者 putAll(Map<UK,UV>) 添加映射。 使用 get(UK) 检索特定 key。 使用 entries(),keys() 和 values() 分别检索映射、键和值的可迭代视图。你还可以通过 isEmpty() 来判断是否包含任何键值对。
以下是MapState和ValueState实例:

public class AvgWithValueState extends RichFlatMapFunction<Tuple2<Long,Long>,Tuple2<Long,Double>> {
    // 求平均数:记录条数  总和
    private transient ValueState<Tuple2<Long,Long>> sum;

    @Override
    public void open(Configuration parameters) throws Exception {
        ValueStateDescriptor<Tuple2<Long,Long>> avg = new ValueStateDescriptor<>("avg", Types.TUPLE(Types.LONG, Types.LONG));
        sum = getRuntimeContext().getState(avg);
    }

    @Override
    public void flatMap(Tuple2<Long, Long> value, Collector<Tuple2<Long, Double>> collector) throws Exception {
        // TODO... ==> state   次数 和 总和
        Tuple2<Long, Long> currentState = sum.value();
        if(null == currentState) {
            currentState = Tuple2.of(0L,0L);
        }

        currentState.f0 += 1; // 次数
        currentState.f1 += value.f1; // 求和


        sum.update(currentState);

        // 达到3条数据 ==> 求平均数  clear
        if(currentState.f0 >=3 ){
            collector.collect(Tuple2.of(value.f0, currentState.f1/currentState.f0.doubleValue()));
            sum.clear();
        }

    }
}
public class AvgWithMapState extends RichFlatMapFunction<Tuple2<Long,Long>,Tuple2<Long,Double>> {
    private transient MapState<String,Long> mapState;

    @Override
    public void open(Configuration parameters) throws Exception {
        MapStateDescriptor<String, Long> descriptor = new MapStateDescriptor<String, Long>("avg", String.class, Long.class);
        mapState = getRuntimeContext().getMapState(descriptor);
    }

    @Override
    public void flatMap(Tuple2<Long, Long> value, Collector<Tuple2<Long, Double>> out) throws Exception {
        mapState.put(UUID.randomUUID().toString(), value.f1);

        ArrayList<Long> elements = Lists.newArrayList(mapState.values());

        if(elements.size() == 3) {
            Long count = 0L;
            Long sum = 0L;

            for (Long element : elements) {
                count += 1;
                sum += element;
            }

            Double avg = sum / count.doubleValue();
            out.collect(Tuple2.of(value.f0, avg));

            mapState.clear();
        }

    }
}
public class StateApp {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        test01(env);
        env.execute();
    }
    /**使用valuestate实现平均数*/
    public static void test01(StreamExecutionEnvironment env) throws Exception {
        ArrayList<Tuple2<Long,Long>> list = new ArrayList<>();
        list.add(Tuple2.of(1L,5L));
        list.add(Tuple2.of(2L,7L));
        list.add(Tuple2.of(1L,4L));
        list.add(Tuple2.of(2L,3L));
        list.add(Tuple2.of(1L,8L));
        list.add(Tuple2.of(2L,2L));
        env.fromCollection(list)
                .keyBy(x -> x.f0)
                //.flatMap(new AvgWithValueState())
                .flatMap(new AvgWithMapState())
                .print();
    }
}

可以看到在使用state的时候是放在增强的flatmap方法里的,这样做的好处就是可以控制state的生命周期。而且写法参考官方给到的,创建一个状态描述器,然后通过runtimecontext上下文对象获取到,如果元素到达三个后通过clear方法清除state。
当然,如果对状态过期处理需要做精细化配置,可以通过ttl进行设置。

StateTtlConfig ttlConfig = StateTtlConfig
    .newBuilder(Time.seconds(1))
    .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
    .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
    .build();
ValueStateDescriptor<String> stateDescriptor = new ValueStateDescriptor<>("text state", String.class);
stateDescriptor.enableTimeToLive(ttlConfig);

在这里插入图片描述

状态容错

上面介绍了怎样使用状态,但有一个问题就是state默认都存储在程序的jvm里,如果程序异常终止,那么state也会被清除。在生产中这显然是不被允许的,所以flink官方推出checkpoint-状态保存机制。
状态持久化存储方式:
1、jvm里,这个是默认配置
2、filesystem,可以是本地文件系统,也可以是hdfs,s3 等
3、rocksdb轻量化数据库里

使用起来也很方便,在初始化环境的时候就可以进行配置checkpoint

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 每 1000ms 开始一次 checkpoint
env.enableCheckpointing(1000);
// 高级选项:
// 设置模式为精确一次 (这是默认值)
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
// 确认 checkpoints 之间的时间会进行 500 ms
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
// Checkpoint 必须在一分钟内完成,否则就会被抛弃
env.getCheckpointConfig().setCheckpointTimeout(60000);
// 同一时间只允许一个 checkpoint 进行
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
// 开启在 job 中止后仍然保留的 externalized checkpoints
env.getCheckpointConfig().enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
//配置checkpoint保存在本地的路径
env.setStateBackend(new FsStateBackend("file:/data/checkpoint/"));

状态实操案例

以下是checkpoint配合重启策略使用。这边需要注意的是,如果任务需要打包提交到集群运行,就需要将以下代码注释。另外在flink集群中找到flink-conf.yaml配置文件中找到backend相关配置,然后做如下修改:
在这里插入图片描述

public class StateBackend {
    public static void main(String[] args) throws Exception{
    //本地模拟根据checkpoint续跑,将ck路径加载到配置里,然后放到env中即可
        //Configuration configuration = new Configuration();
        //configuration.setString("execution.savepoint.path", "/Users/shuofeng/IdeaProjects/Test/Flink_Project/Flink_BasedApi/src/main/java/com/lxf/data/7256e98b3ec6fe887ee15fbedd91202a/chk-43/");
      //  StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(configuration);
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //env.setStateBackend(new EmbeddedRocksDBStateBackend());
        env.enableCheckpointing(5000);
        // 确认 checkpoints 之间的时间会进行 500 ms
        env.getCheckpointConfig().setMinPauseBetweenCheckpoints(5000);

        // Checkpoint 必须在一分钟内完成,否则就会被抛弃
        env.getCheckpointConfig().setCheckpointTimeout(60000);

        // 允许两个连续的 checkpoint 错误
        env.getCheckpointConfig().setTolerableCheckpointFailureNumber(2);

        // 同一时间只允许一个 checkpoint 进行
        env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);

        // 使用 externalized checkpoints,这样 checkpoint 在作业取消后仍就会被保留
        /**
         * ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION: 当作业被取消时,保留外部的checkpoint。注意,在此情况下,您必须手动清理checkpoint状态
         * ExternalizedCheckpointCleanup.DELETE_ON_CANCELLATION: 当作业被取消时,删除外部化的checkpoint。只有当作业失败时,检查点状态才可用。
         */
        env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);

        //env.setStateBackend(new FsStateBackend("file:/Users/shuofeng/IdeaProjects/Test/Flink_Project/Flink_BasedApi/src/main/java/com/lxf/data/checkpoint/"));


        env.setRestartStrategy(RestartStrategies.fixedDelayRestart(
                3, // 尝试重启的次数
                Time.of(5, TimeUnit.SECONDS) // 间隔
        ));


        test01(env);
        env.execute();
    }

    private static void test01(StreamExecutionEnvironment env) {
        DataStreamSource<String> source = env.socketTextStream("110.40.173.142", 9633);
        source.map(new MapFunction<String, String>() {
            @Override
            public String map(String s) throws Exception {
                if(s.contains("DK")){
                    throw new RuntimeException("ERROR....");
                }else {
                    return s.toLowerCase();
                }
            }
        }).flatMap(new FlatMapFunction<String, String>() {
            @Override
            public void flatMap(String s, Collector<String> collector) throws Exception {
                String[] split = s.split(",");
                for (String s1 : split) {
                    collector.collect(s1);
                }
            }
        }).map(new MapFunction<String, Tuple2<String,Integer>>() {
            @Override
            public Tuple2<String, Integer> map(String s) throws Exception {
                return Tuple2.of(s,1);
            }
        }).keyBy(x -> x.f0)
                .sum(1)
                .print();
    }
}

利用maven打包后可以将jar上传至flink的lib下,然后在bin目录下执行:./flink run -c com.lxf.state.StateBackend -s /usr/local/soft/flink-checkpoints/7256e98b3ec6fe887ee15fbedd91202a/chk-43 ~/lib/Flink_BasedApi-1.0-SNAPSHOT.jar
然后按照最新的checkpoint续跑即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值